mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-28 02:45:58 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
aeafae62df
@ -8484,6 +8484,9 @@ Sorry for the inconvenience.";
|
|||||||
"Conversation.SuggestedVideoTextYou" = "You suggested %@ to use this video for their Telegram account.";
|
"Conversation.SuggestedVideoTextYou" = "You suggested %@ to use this video for their Telegram account.";
|
||||||
"Conversation.SuggestedVideoView" = "View";
|
"Conversation.SuggestedVideoView" = "View";
|
||||||
|
|
||||||
|
"CacheEvictionMenu.CategoryExceptions_1" = "%@ Exception";
|
||||||
|
"CacheEvictionMenu.CategoryExceptions_any" = "%@ Exceptions";
|
||||||
|
|
||||||
"Conversation.Messages_1" = "%@ message";
|
"Conversation.Messages_1" = "%@ message";
|
||||||
"Conversation.Messages_any" = "%@ messages";
|
"Conversation.Messages_any" = "%@ messages";
|
||||||
|
|
||||||
|
|||||||
@ -52,8 +52,9 @@ public final class PeerSelectionControllerParams {
|
|||||||
public let multipleSelection: Bool
|
public let multipleSelection: Bool
|
||||||
public let forwardedMessageIds: [EngineMessage.Id]
|
public let forwardedMessageIds: [EngineMessage.Id]
|
||||||
public let hasTypeHeaders: Bool
|
public let hasTypeHeaders: Bool
|
||||||
|
public let selectForumThreads: Bool
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter = [.onlyWriteable], forumPeerId: EnginePeer.Id? = nil, hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer, Int64?) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessageIds: [EngineMessage.Id] = [], hasTypeHeaders: Bool = false) {
|
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter = [.onlyWriteable], forumPeerId: EnginePeer.Id? = nil, hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer, Int64?) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessageIds: [EngineMessage.Id] = [], hasTypeHeaders: Bool = false, selectForumThreads: Bool = false) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
@ -68,6 +69,7 @@ public final class PeerSelectionControllerParams {
|
|||||||
self.multipleSelection = multipleSelection
|
self.multipleSelection = multipleSelection
|
||||||
self.forwardedMessageIds = forwardedMessageIds
|
self.forwardedMessageIds = forwardedMessageIds
|
||||||
self.hasTypeHeaders = hasTypeHeaders
|
self.hasTypeHeaders = hasTypeHeaders
|
||||||
|
self.selectForumThreads = selectForumThreads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1213,7 +1213,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (strongSelf.context.account.postbox.mediaBox.removeCachedResources(resourceIds, force: true, notify: true)
|
let _ = (strongSelf.context.account.postbox.mediaBox.removeCachedResources(Array(resourceIds), force: true, notify: true)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -1298,7 +1298,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
|
|
||||||
self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: messages.values.filter { messageIds.contains($0.id) })
|
self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: messages.values.filter { messageIds.contains($0.id) })
|
||||||
|
|
||||||
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true))
|
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true, selectForumThreads: true))
|
||||||
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in
|
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in
|
||||||
guard let strongSelf = self, let strongController = peerSelectionController else {
|
guard let strongSelf = self, let strongController = peerSelectionController else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -27,6 +27,8 @@ swift_library(
|
|||||||
"//submodules/AnimationUI:AnimationUI",
|
"//submodules/AnimationUI:AnimationUI",
|
||||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||||
|
"//submodules/AvatarNode",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -5,12 +5,22 @@ import AsyncDisplayKit
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
import AvatarNode
|
||||||
|
import TelegramCore
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
|
private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
||||||
|
|
||||||
public enum ItemListDisclosureItemTitleColor {
|
public enum ItemListDisclosureItemTitleColor {
|
||||||
case primary
|
case primary
|
||||||
case accent
|
case accent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ItemListDisclosureItemTitleFont {
|
||||||
|
case regular
|
||||||
|
case bold
|
||||||
|
}
|
||||||
|
|
||||||
public enum ItemListDisclosureStyle {
|
public enum ItemListDisclosureStyle {
|
||||||
case arrow
|
case arrow
|
||||||
case optionArrows
|
case optionArrows
|
||||||
@ -31,11 +41,15 @@ public enum ItemListDisclosureLabelStyle {
|
|||||||
public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let icon: UIImage?
|
let icon: UIImage?
|
||||||
|
let context: AccountContext?
|
||||||
|
let iconPeer: EnginePeer?
|
||||||
let title: String
|
let title: String
|
||||||
let titleColor: ItemListDisclosureItemTitleColor
|
let titleColor: ItemListDisclosureItemTitleColor
|
||||||
|
let titleFont: ItemListDisclosureItemTitleFont
|
||||||
let enabled: Bool
|
let enabled: Bool
|
||||||
let label: String
|
let label: String
|
||||||
let labelStyle: ItemListDisclosureLabelStyle
|
let labelStyle: ItemListDisclosureLabelStyle
|
||||||
|
let additionalDetailLabel: String?
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
let disclosureStyle: ItemListDisclosureStyle
|
let disclosureStyle: ItemListDisclosureStyle
|
||||||
@ -44,14 +58,18 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
|||||||
public let tag: ItemListItemTag?
|
public let tag: ItemListItemTag?
|
||||||
public let shimmeringIndex: Int?
|
public let shimmeringIndex: Int?
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
|
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
self.context = context
|
||||||
|
self.iconPeer = iconPeer
|
||||||
self.title = title
|
self.title = title
|
||||||
self.titleColor = titleColor
|
self.titleColor = titleColor
|
||||||
|
self.titleFont = titleFont
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
self.labelStyle = labelStyle
|
self.labelStyle = labelStyle
|
||||||
self.label = label
|
self.label = label
|
||||||
|
self.additionalDetailLabel = additionalDetailLabel
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
self.disclosureStyle = disclosureStyle
|
self.disclosureStyle = disclosureStyle
|
||||||
@ -115,9 +133,11 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
private let maskNode: ASImageNode
|
private let maskNode: ASImageNode
|
||||||
|
|
||||||
|
var avatarNode: AvatarNode?
|
||||||
let iconNode: ASImageNode
|
let iconNode: ASImageNode
|
||||||
let titleNode: TextNode
|
let titleNode: TextNode
|
||||||
public let labelNode: TextNode
|
public let labelNode: TextNode
|
||||||
|
var additionalDetailLabelNode: TextNode?
|
||||||
let arrowNode: ASImageNode
|
let arrowNode: ASImageNode
|
||||||
let labelBadgeNode: ASImageNode
|
let labelBadgeNode: ASImageNode
|
||||||
let labelImageNode: ASImageNode
|
let labelImageNode: ASImageNode
|
||||||
@ -213,6 +233,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
public func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
public func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
|
let makeAdditionalDetailLabelLayout = TextNode.asyncLayout(self.additionalDetailLabelNode)
|
||||||
|
|
||||||
let currentItem = self.item
|
let currentItem = self.item
|
||||||
|
|
||||||
@ -284,8 +305,10 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
let itemSeparatorColor: UIColor
|
let itemSeparatorColor: UIColor
|
||||||
|
|
||||||
var leftInset = 16.0 + params.leftInset
|
var leftInset = 16.0 + params.leftInset
|
||||||
if let _ = item.icon {
|
if item.icon != nil {
|
||||||
leftInset += 43.0
|
leftInset += 43.0
|
||||||
|
} else if item.iconPeer != nil {
|
||||||
|
leftInset += 46.0
|
||||||
}
|
}
|
||||||
|
|
||||||
var additionalTextRightInset: CGFloat = 0.0
|
var additionalTextRightInset: CGFloat = 0.0
|
||||||
@ -303,15 +326,31 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
titleColor = item.presentationData.theme.list.itemDisabledTextColor
|
titleColor = item.presentationData.theme.list.itemDisabledTextColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
let titleFont: UIFont
|
||||||
|
let defaultLabelFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
|
switch item.titleFont {
|
||||||
|
case .regular:
|
||||||
|
titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
|
case .bold:
|
||||||
|
titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
|
}
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - additionalTextRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
var maxTitleWidth: CGFloat = params.width - params.rightInset - 20.0 - leftInset - additionalTextRightInset
|
||||||
|
if item.iconPeer != nil {
|
||||||
|
maxTitleWidth -= 12.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let detailFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
let detailFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
||||||
|
|
||||||
let labelFont: UIFont
|
let labelFont: UIFont
|
||||||
let labelBadgeColor: UIColor
|
let labelBadgeColor: UIColor
|
||||||
var labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0
|
var labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0
|
||||||
|
if item.iconPeer != nil {
|
||||||
|
labelConstrain -= 6.0
|
||||||
|
}
|
||||||
|
|
||||||
switch item.labelStyle {
|
switch item.labelStyle {
|
||||||
case .badge:
|
case .badge:
|
||||||
labelBadgeColor = item.presentationData.theme.list.itemCheckColors.foregroundColor
|
labelBadgeColor = item.presentationData.theme.list.itemCheckColors.foregroundColor
|
||||||
@ -322,22 +361,33 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
labelConstrain = params.width - params.rightInset - 40.0 - leftInset
|
labelConstrain = params.width - params.rightInset - 40.0 - leftInset
|
||||||
case let .coloredText(color):
|
case let .coloredText(color):
|
||||||
labelBadgeColor = color
|
labelBadgeColor = color
|
||||||
labelFont = titleFont
|
labelFont = defaultLabelFont
|
||||||
default:
|
default:
|
||||||
labelBadgeColor = item.presentationData.theme.list.itemSecondaryTextColor
|
labelBadgeColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||||
labelFont = titleFont
|
labelFont = defaultLabelFont
|
||||||
}
|
}
|
||||||
var multilineLabel = false
|
var multilineLabel = false
|
||||||
if case .multilineDetailText = item.labelStyle {
|
if case .multilineDetailText = item.labelStyle {
|
||||||
multilineLabel = true
|
multilineLabel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor:labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
var additionalDetailLabelInfo: (TextNodeLayout, () -> TextNode)?
|
||||||
|
if let additionalDetailLabel = item.additionalDetailLabel {
|
||||||
|
additionalDetailLabelInfo = makeAdditionalDetailLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: additionalDetailLabel, font: detailFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - additionalTextRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
}
|
||||||
|
|
||||||
|
let verticalInset: CGFloat
|
||||||
|
if item.iconPeer != nil {
|
||||||
|
verticalInset = 6.0
|
||||||
|
} else {
|
||||||
|
verticalInset = 11.0
|
||||||
|
}
|
||||||
|
|
||||||
let verticalInset: CGFloat = 11.0
|
|
||||||
let titleSpacing: CGFloat = 1.0
|
let titleSpacing: CGFloat = 1.0
|
||||||
|
|
||||||
let height: CGFloat
|
var height: CGFloat
|
||||||
switch item.labelStyle {
|
switch item.labelStyle {
|
||||||
case .detailText:
|
case .detailText:
|
||||||
height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height
|
height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height
|
||||||
@ -346,6 +396,12 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
default:
|
default:
|
||||||
height = verticalInset * 2.0 + titleLayout.size.height
|
height = verticalInset * 2.0 + titleLayout.size.height
|
||||||
}
|
}
|
||||||
|
if let additionalDetailLabelInfo = additionalDetailLabelInfo {
|
||||||
|
height += titleSpacing + additionalDetailLabelInfo.0.size.height
|
||||||
|
}
|
||||||
|
if item.iconPeer != nil {
|
||||||
|
height = max(height, 40.0 + verticalInset * 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
switch item.style {
|
switch item.style {
|
||||||
case .plain:
|
case .plain:
|
||||||
@ -394,6 +450,27 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
strongSelf.iconNode.removeFromSupernode()
|
strongSelf.iconNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let context = item.context, let iconPeer = item.iconPeer {
|
||||||
|
let avatarNode: AvatarNode
|
||||||
|
if let current = strongSelf.avatarNode {
|
||||||
|
avatarNode = current
|
||||||
|
} else {
|
||||||
|
avatarNode = AvatarNode(font: avatarFont)
|
||||||
|
strongSelf.avatarNode = avatarNode
|
||||||
|
strongSelf.addSubnode(avatarNode)
|
||||||
|
}
|
||||||
|
let avatarSize: CGFloat = 40.0
|
||||||
|
avatarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - avatarSize) / 2.0), y: floor((height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
|
||||||
|
var clipStyle: AvatarNodeClipStyle = .round
|
||||||
|
if case let .channel(channel) = iconPeer, channel.flags.contains(.isForum) {
|
||||||
|
clipStyle = .roundedRect
|
||||||
|
}
|
||||||
|
avatarNode.setPeer(context: context, theme: item.presentationData.theme, peer: iconPeer, clipStyle: clipStyle)
|
||||||
|
} else if let avatarNode = strongSelf.avatarNode {
|
||||||
|
strongSelf.avatarNode = nil
|
||||||
|
avatarNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
if let updateArrowImage = updateArrowImage {
|
if let updateArrowImage = updateArrowImage {
|
||||||
strongSelf.arrowNode.image = updateArrowImage
|
strongSelf.arrowNode.image = updateArrowImage
|
||||||
}
|
}
|
||||||
@ -466,7 +543,20 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
|
var centralContentHeight: CGFloat = titleLayout.size.height
|
||||||
|
switch item.labelStyle {
|
||||||
|
case .detailText, .multilineDetailText:
|
||||||
|
centralContentHeight += titleSpacing
|
||||||
|
centralContentHeight += labelLayout.size.height
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let additionalDetailLabelInfo {
|
||||||
|
centralContentHeight += titleSpacing
|
||||||
|
centralContentHeight += additionalDetailLabelInfo.0.size.height
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - centralContentHeight) / 2.0)), size: titleLayout.size)
|
||||||
strongSelf.titleNode.frame = titleFrame
|
strongSelf.titleNode.frame = titleFrame
|
||||||
|
|
||||||
if let updateBadgeImage = updatedLabelBadgeImage {
|
if let updateBadgeImage = updatedLabelBadgeImage {
|
||||||
@ -486,15 +576,30 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
|
|
||||||
let labelFrame: CGRect
|
let labelFrame: CGRect
|
||||||
switch item.labelStyle {
|
switch item.labelStyle {
|
||||||
case .badge:
|
case .badge:
|
||||||
labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: badgeFrame.minY + 1), size: labelLayout.size)
|
labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: badgeFrame.minY + 1), size: labelLayout.size)
|
||||||
case .detailText, .multilineDetailText:
|
case .detailText, .multilineDetailText:
|
||||||
labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)
|
labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)
|
||||||
default:
|
default:
|
||||||
labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: 11.0), size: labelLayout.size)
|
labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: floor((height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||||
}
|
}
|
||||||
strongSelf.labelNode.frame = labelFrame
|
strongSelf.labelNode.frame = labelFrame
|
||||||
|
|
||||||
|
if let additionalDetailLabelInfo = additionalDetailLabelInfo {
|
||||||
|
let additionalDetailLabelNode = additionalDetailLabelInfo.1()
|
||||||
|
|
||||||
|
if strongSelf.additionalDetailLabelNode !== additionalDetailLabelNode {
|
||||||
|
strongSelf.additionalDetailLabelNode?.removeFromSupernode()
|
||||||
|
strongSelf.additionalDetailLabelNode = additionalDetailLabelNode
|
||||||
|
strongSelf.addSubnode(additionalDetailLabelNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalDetailLabelNode.frame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: additionalDetailLabelInfo.0.size)
|
||||||
|
} else if let additionalDetailLabelNode = strongSelf.additionalDetailLabelNode {
|
||||||
|
strongSelf.additionalDetailLabelNode = nil
|
||||||
|
additionalDetailLabelNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
if case .textWithIcon = item.labelStyle {
|
if case .textWithIcon = item.labelStyle {
|
||||||
if let updatedLabelImage = updatedLabelImage {
|
if let updatedLabelImage = updatedLabelImage {
|
||||||
strongSelf.labelImageNode.image = updatedLabelImage
|
strongSelf.labelImageNode.image = updatedLabelImage
|
||||||
|
|||||||
@ -194,7 +194,7 @@ public final class MediaBox {
|
|||||||
}), basePath: basePath + "/storage")
|
}), basePath: basePath + "/storage")
|
||||||
|
|
||||||
self.timeBasedCleanup = TimeBasedCleanup(generalPaths: [
|
self.timeBasedCleanup = TimeBasedCleanup(generalPaths: [
|
||||||
self.basePath,
|
//self.basePath,
|
||||||
self.basePath + "/cache",
|
self.basePath + "/cache",
|
||||||
self.basePath + "/animation-cache"
|
self.basePath + "/animation-cache"
|
||||||
], shortLivedPaths: [
|
], shortLivedPaths: [
|
||||||
@ -595,7 +595,7 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let location = parameters?.location {
|
if let location = parameters?.location {
|
||||||
self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId, messageNamespace: UInt8(clamping: location.messageId.namespace), messageId: location.messageId.id), to: resource.id.stringRepresentation.data(using: .utf8)!)
|
self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: location.messageId.namespace), messageId: location.messageId.id), to: resource.id.stringRepresentation.data(using: .utf8)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else {
|
guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else {
|
||||||
@ -761,7 +761,7 @@ public final class MediaBox {
|
|||||||
let paths = self.storePathsForId(resource.id)
|
let paths = self.storePathsForId(resource.id)
|
||||||
|
|
||||||
if let location = parameters?.location {
|
if let location = parameters?.location {
|
||||||
self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId, messageNamespace: UInt8(clamping: location.messageId.namespace), messageId: location.messageId.id), to: resource.id.stringRepresentation.data(using: .utf8)!)
|
self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: location.messageId.namespace), messageId: location.messageId.id), to: resource.id.stringRepresentation.data(using: .utf8)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let _ = fileSize(paths.complete) {
|
if let _ = fileSize(paths.complete) {
|
||||||
@ -1207,6 +1207,24 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func resourceUsageWithInfo(id: MediaResourceId) -> Int32 {
|
||||||
|
let paths = self.storePathsForId(id)
|
||||||
|
|
||||||
|
var value = stat()
|
||||||
|
|
||||||
|
if stat(paths.complete, &value) == 0 {
|
||||||
|
return Int32(value.st_mtimespec.tv_sec)
|
||||||
|
}
|
||||||
|
|
||||||
|
value = stat()
|
||||||
|
|
||||||
|
if stat(paths.partial, &value) == 0 {
|
||||||
|
return Int32(value.st_mtimespec.tv_sec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
public func collectResourceCacheUsage(_ ids: [MediaResourceId]) -> Signal<[MediaResourceId: Int64], NoError> {
|
public func collectResourceCacheUsage(_ ids: [MediaResourceId]) -> Signal<[MediaResourceId: Int64], NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
@ -1472,7 +1490,7 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeCachedResources(_ ids: Set<MediaResourceId>, force: Bool = false, notify: Bool = false) -> Signal<Float, NoError> {
|
public func removeCachedResources(_ ids: [MediaResourceId], force: Bool = false, notify: Bool = false) -> Signal<Float, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
let uniqueIds = Set(ids.map { $0.stringRepresentation })
|
let uniqueIds = Set(ids.map { $0.stringRepresentation })
|
||||||
|
|||||||
@ -8,6 +8,7 @@ private struct SqliteValueBoxTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
||||||
|
let SQLITE_PREPARE_PERSISTENT: UInt32 = 1
|
||||||
|
|
||||||
private func checkTableKey(_ table: ValueBoxTable, _ key: ValueBoxKey) {
|
private func checkTableKey(_ table: ValueBoxTable, _ key: ValueBoxKey) {
|
||||||
switch table.keyType {
|
switch table.keyType {
|
||||||
@ -731,7 +732,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT value FROM t\(table.id) WHERE key=?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT value FROM t\(table.id) WHERE key=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.getStatements[table.id] = preparedStatement
|
self.getStatements[table.id] = preparedStatement
|
||||||
@ -760,7 +761,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT rowid FROM t\(table.id) WHERE key=?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT rowid FROM t\(table.id) WHERE key=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.getRowIdStatements[table.id] = preparedStatement
|
self.getRowIdStatements[table.id] = preparedStatement
|
||||||
@ -790,7 +791,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT ?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT ?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.rangeKeyAscStatementsLimit[table.id] = preparedStatement
|
self.rangeKeyAscStatementsLimit[table.id] = preparedStatement
|
||||||
@ -823,7 +824,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.rangeKeyAscStatementsNoLimit[table.id] = preparedStatement
|
self.rangeKeyAscStatementsNoLimit[table.id] = preparedStatement
|
||||||
@ -854,7 +855,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT ?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT ?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.rangeKeyDescStatementsLimit[table.id] = preparedStatement
|
self.rangeKeyDescStatementsLimit[table.id] = preparedStatement
|
||||||
@ -886,7 +887,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.rangeKeyDescStatementsNoLimit[table.id] = preparedStatement
|
self.rangeKeyDescStatementsNoLimit[table.id] = preparedStatement
|
||||||
@ -918,7 +919,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "DELETE FROM t\(table.id) WHERE key >= ? AND key <= ?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "DELETE FROM t\(table.id) WHERE key >= ? AND key <= ?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.deleteRangeStatements[table.id] = preparedStatement
|
self.deleteRangeStatements[table.id] = preparedStatement
|
||||||
@ -950,7 +951,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT ?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT ?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.rangeValueAscStatementsLimit[table.id] = preparedStatement
|
self.rangeValueAscStatementsLimit[table.id] = preparedStatement
|
||||||
@ -982,7 +983,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key ASC", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.rangeValueAscStatementsNoLimit[table.id] = preparedStatement
|
self.rangeValueAscStatementsNoLimit[table.id] = preparedStatement
|
||||||
@ -1014,7 +1015,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT ?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT ?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.rangeValueDescStatementsLimit[table.id] = preparedStatement
|
self.rangeValueDescStatementsLimit[table.id] = preparedStatement
|
||||||
@ -1047,7 +1048,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key, value FROM t\(table.id) WHERE key > ? AND key < ? ORDER BY key DESC", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.rangeValueDescStatementsNoLimit[table.id] = preparedStatement
|
self.rangeValueDescStatementsNoLimit[table.id] = preparedStatement
|
||||||
@ -1077,7 +1078,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table.id) ORDER BY key ASC", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key, value FROM t\(table.id) ORDER BY key ASC", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.scanStatements[table.id] = preparedStatement
|
self.scanStatements[table.id] = preparedStatement
|
||||||
@ -1098,7 +1099,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table.id) ORDER BY key ASC", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT key FROM t\(table.id) ORDER BY key ASC", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.scanKeysStatements[table.id] = preparedStatement
|
self.scanKeysStatements[table.id] = preparedStatement
|
||||||
@ -1120,7 +1121,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT rowid FROM t\(table.id) WHERE key=?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT rowid FROM t\(table.id) WHERE key=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.existsStatements[table.id] = preparedStatement
|
self.existsStatements[table.id] = preparedStatement
|
||||||
@ -1149,7 +1150,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "UPDATE t\(table.id) SET value=? WHERE key=?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "UPDATE t\(table.id) SET value=? WHERE key=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.updateStatements[table.id] = preparedStatement
|
self.updateStatements[table.id] = preparedStatement
|
||||||
@ -1180,7 +1181,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
if status != SQLITE_OK {
|
if status != SQLITE_OK {
|
||||||
let errorText = self.database.currentError() ?? "Unknown error"
|
let errorText = self.database.currentError() ?? "Unknown error"
|
||||||
preconditionFailure(errorText)
|
preconditionFailure(errorText)
|
||||||
@ -1194,7 +1195,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?)", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?)", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
if status != SQLITE_OK {
|
if status != SQLITE_OK {
|
||||||
let errorText = self.database.currentError() ?? "Unknown error"
|
let errorText = self.database.currentError() ?? "Unknown error"
|
||||||
preconditionFailure(errorText)
|
preconditionFailure(errorText)
|
||||||
@ -1233,7 +1234,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?) ON CONFLICT(key) DO NOTHING", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?) ON CONFLICT(key) DO NOTHING", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
if status != SQLITE_OK {
|
if status != SQLITE_OK {
|
||||||
let errorText = self.database.currentError() ?? "Unknown error"
|
let errorText = self.database.currentError() ?? "Unknown error"
|
||||||
preconditionFailure(errorText)
|
preconditionFailure(errorText)
|
||||||
@ -1247,7 +1248,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?)", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?)", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
if status != SQLITE_OK {
|
if status != SQLITE_OK {
|
||||||
let errorText = self.database.currentError() ?? "Unknown error"
|
let errorText = self.database.currentError() ?? "Unknown error"
|
||||||
preconditionFailure(errorText)
|
preconditionFailure(errorText)
|
||||||
@ -1285,7 +1286,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "DELETE FROM t\(table.id) WHERE key=?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "DELETE FROM t\(table.id) WHERE key=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.deleteStatements[table.id] = preparedStatement
|
self.deleteStatements[table.id] = preparedStatement
|
||||||
@ -1315,7 +1316,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "UPDATE t\(table.id) SET key=? WHERE key=?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "UPDATE t\(table.id) SET key=? WHERE key=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.moveStatements[table.id] = preparedStatement
|
self.moveStatements[table.id] = preparedStatement
|
||||||
@ -1349,7 +1350,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(toTable.id) (key, value) SELECT ?, t\(fromTable.id).value FROM t\(fromTable.id) WHERE t\(fromTable.id).key=?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(toTable.id) (key, value) SELECT ?, t\(fromTable.id).value FROM t\(fromTable.id) WHERE t\(fromTable.id).key=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.copyStatements[TablePairKey(table1: fromTable.id, table2: toTable.id)] = preparedStatement
|
self.copyStatements[TablePairKey(table1: fromTable.id, table2: toTable.id)] = preparedStatement
|
||||||
@ -1384,7 +1385,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "INSERT INTO ft\(table.id) (collectionId, itemId, contents, tags) VALUES(?, ?, ?, ?)", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO ft\(table.id) (collectionId, itemId, contents, tags) VALUES(?, ?, ?, ?)", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.fullTextInsertStatements[table.id] = preparedStatement
|
self.fullTextInsertStatements[table.id] = preparedStatement
|
||||||
@ -1423,7 +1424,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "DELETE FROM ft\(table.id) WHERE itemId=?", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "DELETE FROM ft\(table.id) WHERE itemId=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.fullTextDeleteStatements[table.id] = preparedStatement
|
self.fullTextDeleteStatements[table.id] = preparedStatement
|
||||||
@ -1447,7 +1448,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\"'", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\"'", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
if status != SQLITE_OK {
|
if status != SQLITE_OK {
|
||||||
self.printError()
|
self.printError()
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
@ -1474,7 +1475,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\" AND collectionId:\"' || ? || '\"'", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\" AND collectionId:\"' || ? || '\"'", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.fullTextMatchCollectionStatements[table.id] = preparedStatement
|
self.fullTextMatchCollectionStatements[table.id] = preparedStatement
|
||||||
@ -1503,7 +1504,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultStatement = statement
|
resultStatement = statement
|
||||||
} else {
|
} else {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
let status = sqlite3_prepare_v2(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\" AND collectionId:\"' || ? || '\" AND tags:\"' || ? || '\"'", -1, &statement, nil)
|
let status = sqlite3_prepare_v3(self.database.handle, "SELECT collectionId, itemId FROM ft\(table.id) WHERE ft\(table.id) MATCH 'contents:\"' || ? || '\" AND collectionId:\"' || ? || '\" AND tags:\"' || ? || '\"'", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||||
precondition(status == SQLITE_OK)
|
precondition(status == SQLITE_OK)
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.fullTextMatchCollectionTagsStatements[table.id] = preparedStatement
|
self.fullTextMatchCollectionTagsStatements[table.id] = preparedStatement
|
||||||
|
|||||||
@ -20,11 +20,11 @@ private func md5Hash(_ data: Data) -> HashId {
|
|||||||
|
|
||||||
public final class StorageBox {
|
public final class StorageBox {
|
||||||
public struct Reference {
|
public struct Reference {
|
||||||
public var peerId: PeerId
|
public var peerId: Int64
|
||||||
public var messageNamespace: UInt8
|
public var messageNamespace: UInt8
|
||||||
public var messageId: Int32
|
public var messageId: Int32
|
||||||
|
|
||||||
public init(peerId: PeerId, messageNamespace: UInt8, messageId: Int32) {
|
public init(peerId: Int64, messageNamespace: UInt8, messageId: Int32) {
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.messageNamespace = messageNamespace
|
self.messageNamespace = messageNamespace
|
||||||
self.messageId = messageId
|
self.messageId = messageId
|
||||||
@ -60,6 +60,8 @@ public final class StorageBox {
|
|||||||
let valueBox: SqliteValueBox
|
let valueBox: SqliteValueBox
|
||||||
let hashIdToIdTable: ValueBoxTable
|
let hashIdToIdTable: ValueBoxTable
|
||||||
let idToReferenceTable: ValueBoxTable
|
let idToReferenceTable: ValueBoxTable
|
||||||
|
let peerIdToIdTable: ValueBoxTable
|
||||||
|
let peerIdTable: ValueBoxTable
|
||||||
|
|
||||||
init(queue: Queue, logger: StorageBox.Logger, basePath: String) {
|
init(queue: Queue, logger: StorageBox.Logger, basePath: String) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
@ -80,6 +82,8 @@ public final class StorageBox {
|
|||||||
|
|
||||||
self.hashIdToIdTable = ValueBoxTable(id: 5, keyType: .binary, compactValuesOnCreation: true)
|
self.hashIdToIdTable = ValueBoxTable(id: 5, keyType: .binary, compactValuesOnCreation: true)
|
||||||
self.idToReferenceTable = ValueBoxTable(id: 6, keyType: .binary, compactValuesOnCreation: true)
|
self.idToReferenceTable = ValueBoxTable(id: 6, keyType: .binary, compactValuesOnCreation: true)
|
||||||
|
self.peerIdToIdTable = ValueBoxTable(id: 7, keyType: .binary, compactValuesOnCreation: true)
|
||||||
|
self.peerIdTable = ValueBoxTable(id: 8, keyType: .binary, compactValuesOnCreation: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(reference: Reference, to id: Data) {
|
func add(reference: Reference, to id: Data) {
|
||||||
@ -87,19 +91,154 @@ public final class StorageBox {
|
|||||||
|
|
||||||
let hashId = md5Hash(id)
|
let hashId = md5Hash(id)
|
||||||
|
|
||||||
let mainKey = ValueBoxKey(length: hashId.data.count)
|
let mainKey = ValueBoxKey(length: 16)
|
||||||
|
mainKey.setData(0, value: hashId.data)
|
||||||
self.valueBox.setOrIgnore(self.hashIdToIdTable, key: mainKey, value: MemoryBuffer(data: id))
|
self.valueBox.setOrIgnore(self.hashIdToIdTable, key: mainKey, value: MemoryBuffer(data: id))
|
||||||
|
|
||||||
let idKey = ValueBoxKey(length: hashId.data.count + 8 + 1 + 4)
|
let idKey = ValueBoxKey(length: hashId.data.count + 8 + 1 + 4)
|
||||||
idKey.setData(0, value: hashId.data)
|
idKey.setData(0, value: hashId.data)
|
||||||
idKey.setInt64(hashId.data.count, value: reference.peerId.toInt64())
|
idKey.setInt64(hashId.data.count, value: reference.peerId)
|
||||||
idKey.setUInt8(hashId.data.count + 8, value: reference.messageNamespace)
|
idKey.setUInt8(hashId.data.count + 8, value: reference.messageNamespace)
|
||||||
idKey.setInt32(hashId.data.count + 8 + 1, value: reference.messageId)
|
idKey.setInt32(hashId.data.count + 8 + 1, value: reference.messageId)
|
||||||
self.valueBox.setOrIgnore(self.idToReferenceTable, key: idKey, value: MemoryBuffer())
|
|
||||||
|
var alreadyStored = false
|
||||||
|
if !self.valueBox.exists(self.idToReferenceTable, key: idKey) {
|
||||||
|
self.valueBox.setOrIgnore(self.idToReferenceTable, key: idKey, value: MemoryBuffer())
|
||||||
|
} else {
|
||||||
|
alreadyStored = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyStored {
|
||||||
|
var idInPeerIdStored = false
|
||||||
|
|
||||||
|
let peerIdIdKey = ValueBoxKey(length: 8 + 16)
|
||||||
|
peerIdIdKey.setInt64(0, value: reference.peerId)
|
||||||
|
peerIdIdKey.setData(8, value: hashId.data)
|
||||||
|
var peerIdIdCount: Int32 = 0
|
||||||
|
if let value = self.valueBox.get(self.peerIdToIdTable, key: peerIdIdKey) {
|
||||||
|
idInPeerIdStored = true
|
||||||
|
if value.length == 4 {
|
||||||
|
memcpy(&peerIdIdCount, value.memory, 4)
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peerIdIdCount += 1
|
||||||
|
self.valueBox.set(self.peerIdToIdTable, key: peerIdIdKey, value: MemoryBuffer(memory: &peerIdIdCount, capacity: 4, length: 4, freeWhenDone: false))
|
||||||
|
|
||||||
|
if !idInPeerIdStored {
|
||||||
|
let peerIdKey = ValueBoxKey(length: 8)
|
||||||
|
peerIdKey.setInt64(0, value: reference.peerId)
|
||||||
|
var peerIdCount: Int32 = 0
|
||||||
|
if let value = self.valueBox.get(self.peerIdTable, key: peerIdKey) {
|
||||||
|
if value.length == 4 {
|
||||||
|
memcpy(&peerIdCount, value.memory, 4)
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peerIdCount += 1
|
||||||
|
self.valueBox.set(self.peerIdTable, key: peerIdKey, value: MemoryBuffer(memory: &peerIdCount, capacity: 4, length: 4, freeWhenDone: false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.valueBox.commit()
|
self.valueBox.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func remove(ids: [Data]) {
|
||||||
|
self.valueBox.begin()
|
||||||
|
|
||||||
|
let mainKey = ValueBoxKey(length: 16)
|
||||||
|
let peerIdIdKey = ValueBoxKey(length: 8 + 16)
|
||||||
|
let peerIdKey = ValueBoxKey(length: 8)
|
||||||
|
|
||||||
|
for id in ids {
|
||||||
|
let hashId = md5Hash(id)
|
||||||
|
mainKey.setData(0, value: hashId.data)
|
||||||
|
|
||||||
|
self.valueBox.remove(self.hashIdToIdTable, key: mainKey, secure: false)
|
||||||
|
|
||||||
|
var referenceKeys: [ValueBoxKey] = []
|
||||||
|
self.valueBox.range(self.idToReferenceTable, start: mainKey, end: mainKey.successor, keys: { key in
|
||||||
|
referenceKeys.append(key)
|
||||||
|
return true
|
||||||
|
}, limit: 0)
|
||||||
|
var peerIds = Set<Int64>()
|
||||||
|
for key in referenceKeys {
|
||||||
|
peerIds.insert(key.getInt64(16))
|
||||||
|
self.valueBox.remove(self.idToReferenceTable, key: key, secure: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
for peerId in peerIds {
|
||||||
|
peerIdIdKey.setInt64(0, value: peerId)
|
||||||
|
peerIdIdKey.setData(8, value: hashId.data)
|
||||||
|
|
||||||
|
if self.valueBox.exists(self.peerIdToIdTable, key: peerIdIdKey) {
|
||||||
|
self.valueBox.remove(self.peerIdToIdTable, key: peerIdIdKey, secure: false)
|
||||||
|
|
||||||
|
peerIdKey.setInt64(0, value: peerId)
|
||||||
|
if let value = self.valueBox.get(self.peerIdTable, key: peerIdKey) {
|
||||||
|
var peerIdCount: Int32 = 0
|
||||||
|
if value.length == 4 {
|
||||||
|
memcpy(&peerIdCount, value.memory, 4)
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIdCount -= 1
|
||||||
|
if peerIdCount > 0 {
|
||||||
|
self.valueBox.set(self.peerIdTable, key: peerIdKey, value: MemoryBuffer(memory: &peerIdCount, capacity: 4, length: 4, freeWhenDone: false))
|
||||||
|
} else {
|
||||||
|
self.valueBox.remove(self.peerIdTable, key: peerIdKey, secure: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.valueBox.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func allPeerIds() -> [PeerId] {
|
||||||
|
var result: [PeerId] = []
|
||||||
|
|
||||||
|
self.valueBox.begin()
|
||||||
|
|
||||||
|
self.valueBox.scan(self.peerIdTable, keys: { key in
|
||||||
|
result.append(PeerId(key.getInt64(0)))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
self.valueBox.commit()
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func all(peerId: PeerId) -> [Data] {
|
||||||
|
self.valueBox.begin()
|
||||||
|
|
||||||
|
var hashIds: [Data] = []
|
||||||
|
let peerIdIdKey = ValueBoxKey(length: 8)
|
||||||
|
peerIdIdKey.setInt64(0, value: peerId.toInt64())
|
||||||
|
self.valueBox.range(self.peerIdToIdTable, start: peerIdIdKey, end: peerIdIdKey.successor, keys: { key in
|
||||||
|
hashIds.append(key.getData(8, length: 16))
|
||||||
|
return true
|
||||||
|
}, limit: 0)
|
||||||
|
|
||||||
|
var result: [Data] = []
|
||||||
|
let mainKey = ValueBoxKey(length: 16)
|
||||||
|
for hashId in hashIds {
|
||||||
|
mainKey.setData(0, value: hashId)
|
||||||
|
if let value = self.valueBox.get(self.hashIdToIdTable, key: mainKey) {
|
||||||
|
result.append(value.makeData())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.valueBox.commit()
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func all() -> [Entry] {
|
func all() -> [Entry] {
|
||||||
var result: [Entry] = []
|
var result: [Entry] = []
|
||||||
|
|
||||||
@ -111,7 +250,7 @@ public final class StorageBox {
|
|||||||
self.valueBox.scan(self.idToReferenceTable, keys: { key in
|
self.valueBox.scan(self.idToReferenceTable, keys: { key in
|
||||||
let id = key.getData(0, length: 16)
|
let id = key.getData(0, length: 16)
|
||||||
|
|
||||||
let peerId = PeerId(key.getInt64(16))
|
let peerId = key.getInt64(16)
|
||||||
let messageNamespace: UInt8 = key.getUInt8(16 + 8)
|
let messageNamespace: UInt8 = key.getUInt8(16 + 8)
|
||||||
let messageId = key.getInt32(16 + 8 + 1)
|
let messageId = key.getInt32(16 + 8 + 1)
|
||||||
|
|
||||||
@ -148,7 +287,7 @@ public final class StorageBox {
|
|||||||
idKey.setData(0, value: hashId.data)
|
idKey.setData(0, value: hashId.data)
|
||||||
var currentReferences: [Reference] = []
|
var currentReferences: [Reference] = []
|
||||||
self.valueBox.range(self.idToReferenceTable, start: idKey, end: idKey.successor, keys: { key in
|
self.valueBox.range(self.idToReferenceTable, start: idKey, end: idKey.successor, keys: { key in
|
||||||
let peerId = PeerId(key.getInt64(16))
|
let peerId = key.getInt64(16)
|
||||||
let messageNamespace: UInt8 = key.getUInt8(16 + 8)
|
let messageNamespace: UInt8 = key.getUInt8(16 + 8)
|
||||||
let messageId = key.getInt32(16 + 8 + 1)
|
let messageId = key.getInt32(16 + 8 + 1)
|
||||||
|
|
||||||
@ -185,6 +324,12 @@ public final class StorageBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func remove(ids: [Data]) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.remove(ids: ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func all() -> Signal<[Entry], NoError> {
|
public func all() -> Signal<[Entry], NoError> {
|
||||||
return self.impl.signalWith { impl, subscriber in
|
return self.impl.signalWith { impl, subscriber in
|
||||||
subscriber.putNext(impl.all())
|
subscriber.putNext(impl.all())
|
||||||
@ -194,6 +339,24 @@ public final class StorageBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func allPeerIds() -> Signal<[PeerId], NoError> {
|
||||||
|
return self.impl.signalWith { impl, subscriber in
|
||||||
|
subscriber.putNext(impl.allPeerIds())
|
||||||
|
subscriber.putCompletion()
|
||||||
|
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func all(peerId: PeerId) -> Signal<[Data], NoError> {
|
||||||
|
return self.impl.signalWith { impl, subscriber in
|
||||||
|
subscriber.putNext(impl.all(peerId: peerId))
|
||||||
|
subscriber.putCompletion()
|
||||||
|
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func get(ids: [Data]) -> Signal<[Entry], NoError> {
|
public func get(ids: [Data]) -> Signal<[Entry], NoError> {
|
||||||
return self.impl.signalWith { impl, subscriber in
|
return self.impl.signalWith { impl, subscriber in
|
||||||
subscriber.putNext(impl.get(ids: ids))
|
subscriber.putNext(impl.get(ids: ids))
|
||||||
|
|||||||
@ -108,6 +108,7 @@ swift_library(
|
|||||||
"//submodules/PersistentStringHash:PersistentStringHash",
|
"//submodules/PersistentStringHash:PersistentStringHash",
|
||||||
"//submodules/TelegramUI/Components/NotificationPeerExceptionController",
|
"//submodules/TelegramUI/Components/NotificationPeerExceptionController",
|
||||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||||
|
"//submodules/AnimatedAvatarSetNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -17,6 +17,8 @@ import DeleteChatPeerActionSheetItem
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
|
import ContextUI
|
||||||
|
import AnimatedAvatarSetNode
|
||||||
|
|
||||||
private func totalDiskSpace() -> Int64 {
|
private func totalDiskSpace() -> Int64 {
|
||||||
do {
|
do {
|
||||||
@ -44,8 +46,9 @@ private final class StorageUsageControllerArguments {
|
|||||||
let openPeerMedia: (PeerId) -> Void
|
let openPeerMedia: (PeerId) -> Void
|
||||||
let clearPeerMedia: (PeerId) -> Void
|
let clearPeerMedia: (PeerId) -> Void
|
||||||
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||||
|
let openCategoryMenu: (StorageUsageEntryTag) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, updateKeepMediaTimeout: @escaping (Int32) -> Void, updateMaximumCacheSize: @escaping (Int32) -> Void, openClearAll: @escaping () -> Void, openPeerMedia: @escaping (PeerId) -> Void, clearPeerMedia: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
|
init(context: AccountContext, updateKeepMediaTimeout: @escaping (Int32) -> Void, updateMaximumCacheSize: @escaping (Int32) -> Void, openClearAll: @escaping () -> Void, openPeerMedia: @escaping (PeerId) -> Void, clearPeerMedia: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, openCategoryMenu: @escaping (StorageUsageEntryTag) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updateKeepMediaTimeout = updateKeepMediaTimeout
|
self.updateKeepMediaTimeout = updateKeepMediaTimeout
|
||||||
self.updateMaximumCacheSize = updateMaximumCacheSize
|
self.updateMaximumCacheSize = updateMaximumCacheSize
|
||||||
@ -53,6 +56,7 @@ private final class StorageUsageControllerArguments {
|
|||||||
self.openPeerMedia = openPeerMedia
|
self.openPeerMedia = openPeerMedia
|
||||||
self.clearPeerMedia = clearPeerMedia
|
self.clearPeerMedia = clearPeerMedia
|
||||||
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
||||||
|
self.openCategoryMenu = openCategoryMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +67,27 @@ private enum StorageUsageSection: Int32 {
|
|||||||
case peers
|
case peers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum StorageUsageEntryTag: Hashable, ItemListItemTag {
|
||||||
|
case privateChats
|
||||||
|
case groups
|
||||||
|
case channels
|
||||||
|
|
||||||
|
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
|
if let other = other as? StorageUsageEntryTag, self == other {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum StorageUsageEntry: ItemListNodeEntry {
|
private enum StorageUsageEntry: ItemListNodeEntry {
|
||||||
case keepMediaHeader(PresentationTheme, String)
|
case keepMediaHeader(PresentationTheme, String)
|
||||||
|
|
||||||
|
case keepMediaPrivateChats(title: String, text: String?, value: String)
|
||||||
|
case keepMediaGroups(title: String, text: String?, value: String)
|
||||||
|
case keepMediaChannels(title: String, text: String?, value: String)
|
||||||
|
|
||||||
case keepMedia(PresentationTheme, PresentationStrings, Int32)
|
case keepMedia(PresentationTheme, PresentationStrings, Int32)
|
||||||
case keepMediaInfo(PresentationTheme, String)
|
case keepMediaInfo(PresentationTheme, String)
|
||||||
|
|
||||||
@ -82,43 +105,49 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .keepMediaHeader, .keepMedia, .keepMediaInfo:
|
case .keepMediaHeader, .keepMedia, .keepMediaInfo, .keepMediaPrivateChats, .keepMediaGroups, .keepMediaChannels:
|
||||||
return StorageUsageSection.keepMedia.rawValue
|
return StorageUsageSection.keepMedia.rawValue
|
||||||
case .maximumSizeHeader, .maximumSize, .maximumSizeInfo:
|
case .maximumSizeHeader, .maximumSize, .maximumSizeInfo:
|
||||||
return StorageUsageSection.maximumSize.rawValue
|
return StorageUsageSection.maximumSize.rawValue
|
||||||
case .storageHeader, .storageUsage, .collecting, .clearAll:
|
case .storageHeader, .storageUsage, .collecting, .clearAll:
|
||||||
return StorageUsageSection.storage.rawValue
|
return StorageUsageSection.storage.rawValue
|
||||||
case .peersHeader, .peer:
|
case .peersHeader, .peer:
|
||||||
return StorageUsageSection.peers.rawValue
|
return StorageUsageSection.peers.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stableId: Int32 {
|
var stableId: Int32 {
|
||||||
switch self {
|
switch self {
|
||||||
case .keepMediaHeader:
|
case .keepMediaHeader:
|
||||||
return 0
|
return 0
|
||||||
case .keepMedia:
|
case .keepMedia:
|
||||||
return 1
|
return 1
|
||||||
case .keepMediaInfo:
|
case .keepMediaPrivateChats:
|
||||||
return 2
|
return 2
|
||||||
case .maximumSizeHeader:
|
case .keepMediaGroups:
|
||||||
return 3
|
return 3
|
||||||
case .maximumSize:
|
case .keepMediaChannels:
|
||||||
return 4
|
return 4
|
||||||
case .maximumSizeInfo:
|
case .keepMediaInfo:
|
||||||
return 5
|
return 5
|
||||||
case .storageHeader:
|
case .maximumSizeHeader:
|
||||||
return 6
|
return 6
|
||||||
case .storageUsage:
|
case .maximumSize:
|
||||||
return 7
|
return 7
|
||||||
case .collecting:
|
case .maximumSizeInfo:
|
||||||
return 8
|
return 8
|
||||||
case .clearAll:
|
case .storageHeader:
|
||||||
return 9
|
return 9
|
||||||
case .peersHeader:
|
case .storageUsage:
|
||||||
return 10
|
return 10
|
||||||
case let .peer(index, _, _, _, _, _, _, _, _):
|
case .collecting:
|
||||||
return 11 + index
|
return 11
|
||||||
|
case .clearAll:
|
||||||
|
return 12
|
||||||
|
case .peersHeader:
|
||||||
|
return 13
|
||||||
|
case let .peer(index, _, _, _, _, _, _, _, _):
|
||||||
|
return 14 + index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +171,24 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .keepMediaPrivateChats(title, text, value):
|
||||||
|
if case .keepMediaPrivateChats(title, text, value) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .keepMediaGroups(title, text, value):
|
||||||
|
if case .keepMediaGroups(title, text, value) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .keepMediaChannels(title, text, value):
|
||||||
|
if case .keepMediaChannels(title, text, value) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .maximumSizeHeader(lhsTheme, lhsText):
|
case let .maximumSizeHeader(lhsTheme, lhsText):
|
||||||
if case let .maximumSizeHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .maximumSizeHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -235,6 +282,18 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .keepMediaHeader(_, text):
|
case let .keepMediaHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
|
case let .keepMediaPrivateChats(title, text, value):
|
||||||
|
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/EditProfile")?.precomposed(), title: title, enabled: true, label: value, labelStyle: .text, additionalDetailLabel: text, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
||||||
|
arguments.openCategoryMenu(.privateChats)
|
||||||
|
}, tag: StorageUsageEntryTag.privateChats)
|
||||||
|
case let .keepMediaGroups(title, text, value):
|
||||||
|
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/GroupChats")?.precomposed(), title: title, enabled: true, label: value, labelStyle: .text, additionalDetailLabel: text, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
||||||
|
arguments.openCategoryMenu(.groups)
|
||||||
|
}, tag: StorageUsageEntryTag.groups)
|
||||||
|
case let .keepMediaChannels(title, text, value):
|
||||||
|
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Channels")?.precomposed(), title: title, enabled: true, label: value, labelStyle: .text, additionalDetailLabel: text, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
||||||
|
arguments.openCategoryMenu(.channels)
|
||||||
|
}, tag: StorageUsageEntryTag.channels)
|
||||||
case let .keepMedia(theme, strings, value):
|
case let .keepMedia(theme, strings, value):
|
||||||
return KeepMediaDurationPickerItem(theme: theme, strings: strings, value: value, sectionId: self.section, updated: { updatedValue in
|
return KeepMediaDurationPickerItem(theme: theme, strings: strings, value: value, sectionId: self.section, updated: { updatedValue in
|
||||||
arguments.updateKeepMediaTimeout(updatedValue)
|
arguments.updateKeepMediaTimeout(updatedValue)
|
||||||
@ -279,18 +338,46 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct StorageUsageState: Equatable {
|
private struct StorageUsageState: Equatable {
|
||||||
let peerIdWithRevealedOptions: PeerId?
|
var peerIdWithRevealedOptions: PeerId?
|
||||||
|
|
||||||
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> StorageUsageState {
|
|
||||||
return StorageUsageState(peerIdWithRevealedOptions: peerIdWithRevealedOptions)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func storageUsageControllerEntries(presentationData: PresentationData, cacheSettings: CacheStorageSettings, cacheStats: CacheUsageStatsResult?, state: StorageUsageState) -> [StorageUsageEntry] {
|
private func storageUsageControllerEntries(presentationData: PresentationData, cacheSettings: CacheStorageSettings, accountSpecificCacheSettings: AccountSpecificCacheStorageSettings, cacheStats: CacheUsageStatsResult?, state: StorageUsageState) -> [StorageUsageEntry] {
|
||||||
var entries: [StorageUsageEntry] = []
|
var entries: [StorageUsageEntry] = []
|
||||||
|
|
||||||
entries.append(.keepMediaHeader(presentationData.theme, presentationData.strings.Cache_KeepMedia.uppercased()))
|
entries.append(.keepMediaHeader(presentationData.theme, presentationData.strings.Cache_KeepMedia.uppercased()))
|
||||||
entries.append(.keepMedia(presentationData.theme, presentationData.strings, cacheSettings.defaultCacheStorageTimeout))
|
|
||||||
|
let sections: [StorageUsageEntryTag] = [.privateChats, .groups, .channels]
|
||||||
|
for section in sections {
|
||||||
|
let mappedCategory: CacheStorageSettings.PeerStorageCategory
|
||||||
|
switch section {
|
||||||
|
case .privateChats:
|
||||||
|
mappedCategory = .privateChats
|
||||||
|
case .groups:
|
||||||
|
mappedCategory = .groups
|
||||||
|
case .channels:
|
||||||
|
mappedCategory = .channels
|
||||||
|
}
|
||||||
|
let value = cacheSettings.categoryStorageTimeout[mappedCategory] ?? Int32.max
|
||||||
|
|
||||||
|
let optionText: String
|
||||||
|
if value == Int32.max {
|
||||||
|
optionText = presentationData.strings.ClearCache_Forever
|
||||||
|
} else {
|
||||||
|
optionText = timeIntervalString(strings: presentationData.strings, value: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch section {
|
||||||
|
case .privateChats:
|
||||||
|
entries.append(.keepMediaPrivateChats(title: presentationData.strings.Notifications_PrivateChats, text: nil, value: optionText))
|
||||||
|
case .groups:
|
||||||
|
entries.append(.keepMediaGroups(title: presentationData.strings.Notifications_GroupChats, text: nil, value: optionText))
|
||||||
|
case .channels:
|
||||||
|
entries.append(.keepMediaChannels(title: presentationData.strings.Notifications_Channels, text: nil, value: optionText))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//entries.append(.keepMedia(presentationData.theme, presentationData.strings, cacheSettings.defaultCacheStorageTimeout))
|
||||||
|
|
||||||
entries.append(.keepMediaInfo(presentationData.theme, presentationData.strings.Cache_KeepMediaHelp))
|
entries.append(.keepMediaInfo(presentationData.theme, presentationData.strings.Cache_KeepMediaHelp))
|
||||||
|
|
||||||
entries.append(.maximumSizeHeader(presentationData.theme, presentationData.strings.Cache_MaximumCacheSize.uppercased()))
|
entries.append(.maximumSizeHeader(presentationData.theme, presentationData.strings.Cache_MaximumCacheSize.uppercased()))
|
||||||
@ -420,7 +507,24 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
return cacheSettings
|
return cacheSettings
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let accountSpecificCacheSettingsPromise = Promise<AccountSpecificCacheStorageSettings>()
|
||||||
|
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings]))
|
||||||
|
accountSpecificCacheSettingsPromise.set(context.account.postbox.combinedView(keys: [viewKey])
|
||||||
|
|> map { views -> AccountSpecificCacheStorageSettings in
|
||||||
|
let cacheSettings: AccountSpecificCacheStorageSettings
|
||||||
|
if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) {
|
||||||
|
cacheSettings = value
|
||||||
|
} else {
|
||||||
|
cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheSettings
|
||||||
|
})
|
||||||
|
|
||||||
var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)?
|
var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)?
|
||||||
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
|
var findAutoremoveReferenceNode: ((StorageUsageEntryTag) -> ItemListDisclosureItemNode?)?
|
||||||
|
var presentInGlobalOverlay: ((ViewController) -> Void)?
|
||||||
|
|
||||||
var statsPromise: Promise<CacheUsageStatsResult?>
|
var statsPromise: Promise<CacheUsageStatsResult?>
|
||||||
if let cacheUsagePromise = cacheUsagePromise {
|
if let cacheUsagePromise = cacheUsagePromise {
|
||||||
@ -441,11 +545,15 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
|
|
||||||
let arguments = StorageUsageControllerArguments(context: context, updateKeepMediaTimeout: { value in
|
let arguments = StorageUsageControllerArguments(context: context, updateKeepMediaTimeout: { value in
|
||||||
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||||
return current.withUpdatedDefaultCacheStorageTimeout(value)
|
var current = current
|
||||||
|
current.defaultCacheStorageTimeout = value
|
||||||
|
return current
|
||||||
}).start()
|
}).start()
|
||||||
}, updateMaximumCacheSize: { value in
|
}, updateMaximumCacheSize: { value in
|
||||||
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||||
return current.withUpdatedDefaultCacheStorageLimitGigabytes(value)
|
var current = current
|
||||||
|
current.defaultCacheStorageLimitGigabytes = value
|
||||||
|
return current
|
||||||
}).start()
|
}).start()
|
||||||
}, openClearAll: {
|
}, openClearAll: {
|
||||||
let _ = (statsPromise.get()
|
let _ = (statsPromise.get()
|
||||||
@ -957,28 +1065,197 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
})
|
})
|
||||||
|
|
||||||
updateState { state in
|
updateState { state in
|
||||||
return state.withUpdatedPeerIdWithRevealedOptions(nil)
|
var state = state
|
||||||
|
state.peerIdWithRevealedOptions = nil
|
||||||
|
return state
|
||||||
}
|
}
|
||||||
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||||
updateState { state in
|
updateState { state in
|
||||||
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
||||||
return state.withUpdatedPeerIdWithRevealedOptions(peerId)
|
var state = state
|
||||||
|
state.peerIdWithRevealedOptions = peerId
|
||||||
|
return state
|
||||||
} else {
|
} else {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, openCategoryMenu: { category in
|
||||||
|
let mappedCategory: CacheStorageSettings.PeerStorageCategory
|
||||||
|
switch category {
|
||||||
|
case .privateChats:
|
||||||
|
mappedCategory = .privateChats
|
||||||
|
case .groups:
|
||||||
|
mappedCategory = .groups
|
||||||
|
case .channels:
|
||||||
|
mappedCategory = .channels
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings]))
|
||||||
|
let accountSpecificSettings: Signal<AccountSpecificCacheStorageSettings, NoError> = context.account.postbox.combinedView(keys: [viewKey])
|
||||||
|
|> map { views -> AccountSpecificCacheStorageSettings in
|
||||||
|
let cacheSettings: AccountSpecificCacheStorageSettings
|
||||||
|
if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) {
|
||||||
|
cacheSettings = value
|
||||||
|
} else {
|
||||||
|
cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheSettings
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|
||||||
|
let peerExceptions: Signal<[(peer: FoundPeer, value: Int32)], NoError> = accountSpecificSettings
|
||||||
|
|> mapToSignal { accountSpecificSettings -> Signal<[(peer: FoundPeer, value: Int32)], NoError> in
|
||||||
|
return context.account.postbox.transaction { transaction -> [(peer: FoundPeer, value: Int32)] in
|
||||||
|
var result: [(peer: FoundPeer, value: Int32)] = []
|
||||||
|
|
||||||
|
for (peerId, value) in accountSpecificSettings.peerStorageTimeoutExceptions {
|
||||||
|
guard let peer = transaction.getPeer(peerId) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let peerCategory: CacheStorageSettings.PeerStorageCategory
|
||||||
|
var subscriberCount: Int32?
|
||||||
|
if peer is TelegramUser {
|
||||||
|
peerCategory = .privateChats
|
||||||
|
} else if peer is TelegramGroup {
|
||||||
|
peerCategory = .groups
|
||||||
|
|
||||||
|
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedGroupData {
|
||||||
|
subscriberCount = (cachedData.participants?.participants.count).flatMap(Int32.init)
|
||||||
|
}
|
||||||
|
} else if let channel = peer as? TelegramChannel {
|
||||||
|
if case .group = channel.info {
|
||||||
|
peerCategory = .groups
|
||||||
|
} else {
|
||||||
|
peerCategory = .channels
|
||||||
|
}
|
||||||
|
if peerCategory == mappedCategory {
|
||||||
|
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData {
|
||||||
|
subscriberCount = cachedData.participantsSummary.memberCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerCategory != mappedCategory {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append((peer: FoundPeer(peer: peer, subscribers: subscriberCount), value: value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.sorted(by: { lhs, rhs in
|
||||||
|
if lhs.value != rhs.value {
|
||||||
|
return lhs.value < rhs.value
|
||||||
|
}
|
||||||
|
return lhs.peer.peer.debugDisplayTitle < rhs.peer.peer.debugDisplayTitle
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (combineLatest(
|
||||||
|
cacheSettingsPromise.get() |> take(1),
|
||||||
|
peerExceptions |> take(1)
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { cacheSettings, peerExceptions in
|
||||||
|
let currentValue: Int32 = cacheSettings.categoryStorageTimeout[mappedCategory] ?? Int32.max
|
||||||
|
|
||||||
|
let applyValue: (Int32) -> Void = { value in
|
||||||
|
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { cacheSettings in
|
||||||
|
var cacheSettings = cacheSettings
|
||||||
|
cacheSettings.categoryStorageTimeout[mappedCategory] = value
|
||||||
|
return cacheSettings
|
||||||
|
}).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
var subItems: [ContextMenuItem] = []
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
var presetValues: [Int32] = [
|
||||||
|
Int32.max,
|
||||||
|
31 * 24 * 60 * 60,
|
||||||
|
7 * 24 * 60 * 60,
|
||||||
|
1 * 24 * 60 * 60
|
||||||
|
]
|
||||||
|
if currentValue != 0 && !presetValues.contains(currentValue) {
|
||||||
|
presetValues.append(currentValue)
|
||||||
|
presetValues.sort(by: >)
|
||||||
|
}
|
||||||
|
|
||||||
|
for value in presetValues {
|
||||||
|
let optionText: String
|
||||||
|
if value == Int32.max {
|
||||||
|
optionText = presentationData.strings.ClearCache_Forever
|
||||||
|
} else {
|
||||||
|
optionText = timeIntervalString(strings: presentationData.strings, value: value)
|
||||||
|
}
|
||||||
|
subItems.append(.action(ContextMenuActionItem(text: optionText, icon: { theme in
|
||||||
|
if currentValue == value {
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, action: { _, f in
|
||||||
|
applyValue(value)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
subItems.append(.separator)
|
||||||
|
|
||||||
|
if peerExceptions.isEmpty {
|
||||||
|
let exceptionsText = presentationData.strings.GroupInfo_Permissions_AddException
|
||||||
|
subItems.append(.action(ContextMenuActionItem(text: exceptionsText, icon: { theme in
|
||||||
|
if case .privateChats = category {
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor)
|
||||||
|
} else {
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Location/CreateGroupIcon"), color: theme.contextMenu.primaryColor)
|
||||||
|
}
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
pushControllerImpl?(storageUsageExceptionsScreen(context: context, category: mappedCategory))
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
subItems.append(.custom(MultiplePeerAvatarsContextItem(context: context, peers: peerExceptions.prefix(3).map { EnginePeer($0.peer.peer) }, action: { c, _ in
|
||||||
|
c.dismiss(completion: {
|
||||||
|
|
||||||
|
})
|
||||||
|
pushControllerImpl?(storageUsageExceptionsScreen(context: context, category: mappedCategory))
|
||||||
|
}), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let sourceNode = findAutoremoveReferenceNode?(category) {
|
||||||
|
let items: Signal<ContextController.Items, NoError> = .single(ContextController.Items(content: .list(subItems)))
|
||||||
|
let source: ContextContentSource = .reference(StorageUsageContextReferenceContentSource(sourceView: sourceNode.labelNode.view))
|
||||||
|
|
||||||
|
let contextController = ContextController(
|
||||||
|
account: context.account,
|
||||||
|
presentationData: presentationData,
|
||||||
|
source: source,
|
||||||
|
items: items,
|
||||||
|
gesture: nil
|
||||||
|
)
|
||||||
|
sourceNode.updateHasContextMenu(hasContextMenu: true)
|
||||||
|
contextController.dismissed = { [weak sourceNode] in
|
||||||
|
sourceNode?.updateHasContextMenu(hasContextMenu: false)
|
||||||
|
}
|
||||||
|
presentInGlobalOverlay?(contextController)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
|
|
||||||
let signal = combineLatest(context.sharedContext.presentationData, cacheSettingsPromise.get(), statsPromise.get(), statePromise.get()) |> deliverOnMainQueue
|
let signal = combineLatest(context.sharedContext.presentationData, cacheSettingsPromise.get(), accountSpecificCacheSettingsPromise.get(), statsPromise.get(), statePromise.get()) |> deliverOnMainQueue
|
||||||
|> map { presentationData, cacheSettings, cacheStats, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, cacheSettings, accountSpecificCacheSettings, cacheStats, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}) : nil
|
}) : nil
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Cache_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Cache_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: storageUsageControllerEntries(presentationData: presentationData, cacheSettings: cacheSettings, cacheStats: cacheStats, state: state), style: .blocks, emptyStateItem: nil, animateChanges: false)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: storageUsageControllerEntries(presentationData: presentationData, cacheSettings: cacheSettings, accountSpecificCacheSettings: accountSpecificCacheSettings, cacheStats: cacheStats, state: state), style: .blocks, emptyStateItem: nil, animateChanges: false)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
} |> afterDisposed {
|
} |> afterDisposed {
|
||||||
@ -993,6 +1270,34 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
presentControllerImpl = { [weak controller] c, contextType, a in
|
presentControllerImpl = { [weak controller] c, contextType, a in
|
||||||
controller?.present(c, in: contextType, with: a)
|
controller?.present(c, in: contextType, with: a)
|
||||||
}
|
}
|
||||||
|
pushControllerImpl = { [weak controller] c in
|
||||||
|
controller?.push(c)
|
||||||
|
}
|
||||||
|
presentInGlobalOverlay = { [weak controller] c in
|
||||||
|
controller?.presentInGlobalOverlay(c, with: nil)
|
||||||
|
}
|
||||||
|
findAutoremoveReferenceNode = { [weak controller] category in
|
||||||
|
guard let controller else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetTag: StorageUsageEntryTag = category
|
||||||
|
var resultItemNode: ItemListItemNode?
|
||||||
|
controller.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? ItemListItemNode {
|
||||||
|
if let tag = itemNode.tag, tag.isEqual(to: targetTag) {
|
||||||
|
resultItemNode = itemNode
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let resultItemNode = resultItemNode as? ItemListDisclosureItemNode {
|
||||||
|
return resultItemNode
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
dismissImpl = { [weak controller] in
|
dismissImpl = { [weak controller] in
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
}
|
}
|
||||||
@ -1110,3 +1415,215 @@ private class StorageUsageClearProgressOverlayNode: ASDisplayNode, ActionSheetGr
|
|||||||
self.animationNode.updateLayout(size: imageSize)
|
self.animationNode.updateLayout(size: imageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class StorageUsageContextReferenceContentSource: ContextReferenceContentSource {
|
||||||
|
private let sourceView: UIView
|
||||||
|
|
||||||
|
init(sourceView: UIView) {
|
||||||
|
self.sourceView = sourceView
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||||
|
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MultiplePeerAvatarsContextItem: ContextMenuCustomItem {
|
||||||
|
fileprivate let context: AccountContext
|
||||||
|
fileprivate let peers: [EnginePeer]
|
||||||
|
fileprivate let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
|
||||||
|
|
||||||
|
init(context: AccountContext, peers: [EnginePeer], action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
|
||||||
|
self.context = context
|
||||||
|
self.peers = peers
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||||
|
return MultiplePeerAvatarsContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class MultiplePeerAvatarsContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol {
|
||||||
|
private let item: MultiplePeerAvatarsContextItem
|
||||||
|
private var presentationData: PresentationData
|
||||||
|
private let getController: () -> ContextControllerProtocol?
|
||||||
|
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||||
|
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
private let textNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private let avatarsNode: AnimatedAvatarSetNode
|
||||||
|
private let avatarsContext: AnimatedAvatarSetContext
|
||||||
|
|
||||||
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
|
|
||||||
|
private var pointerInteraction: PointerInteraction?
|
||||||
|
|
||||||
|
init(presentationData: PresentationData, item: MultiplePeerAvatarsContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||||
|
self.item = item
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.getController = getController
|
||||||
|
self.actionSelected = actionSelected
|
||||||
|
|
||||||
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isAccessibilityElement = false
|
||||||
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
|
self.highlightedBackgroundNode.isAccessibilityElement = false
|
||||||
|
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
|
||||||
|
self.textNode = ImmediateTextNode()
|
||||||
|
self.textNode.isAccessibilityElement = false
|
||||||
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
self.textNode.displaysAsynchronously = false
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: " ", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
self.textNode.maximumNumberOfLines = 1
|
||||||
|
|
||||||
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
|
self.buttonNode.isAccessibilityElement = true
|
||||||
|
self.buttonNode.accessibilityLabel = presentationData.strings.VoiceChat_StopRecording
|
||||||
|
|
||||||
|
self.avatarsNode = AnimatedAvatarSetNode()
|
||||||
|
self.avatarsContext = AnimatedAvatarSetContext()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.addSubnode(self.highlightedBackgroundNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.avatarsNode)
|
||||||
|
self.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
|
self.buttonNode.highligthedChanged = { [weak self] highligted in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if highligted {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
} else {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.buttonNode.isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.75
|
||||||
|
}
|
||||||
|
}, willExit: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private var validLayout: (calculatedWidth: CGFloat, size: CGSize)?
|
||||||
|
|
||||||
|
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||||
|
let sideInset: CGFloat = 14.0
|
||||||
|
let verticalInset: CGFloat = 12.0
|
||||||
|
|
||||||
|
let rightTextInset: CGFloat = sideInset + 36.0
|
||||||
|
|
||||||
|
let calculatedWidth = min(constrainedWidth, 250.0)
|
||||||
|
|
||||||
|
let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
let text: String = self.presentationData.strings.CacheEvictionMenu_CategoryExceptions(Int32(self.item.peers.count))
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
|
||||||
|
|
||||||
|
let textSize = self.textNode.updateLayout(CGSize(width: calculatedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
let combinedTextHeight = textSize.height
|
||||||
|
return (CGSize(width: calculatedWidth, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in
|
||||||
|
self.validLayout = (calculatedWidth: calculatedWidth, size: size)
|
||||||
|
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
|
||||||
|
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||||
|
|
||||||
|
let avatarsContent: AnimatedAvatarSetContext.Content
|
||||||
|
|
||||||
|
let avatarsPeers: [EnginePeer] = self.item.peers
|
||||||
|
|
||||||
|
avatarsContent = self.avatarsContext.update(peers: avatarsPeers, animated: false)
|
||||||
|
|
||||||
|
let avatarsSize = self.avatarsNode.update(context: self.item.context, content: avatarsContent, itemSize: CGSize(width: 24.0, height: 24.0), customSpacing: 10.0, animated: false, synchronousLoad: true)
|
||||||
|
self.avatarsNode.frame = CGRect(origin: CGPoint(x: size.width - sideInset - 12.0 - avatarsSize.width, y: floor((size.height - avatarsSize.height) / 2.0)), size: avatarsSize)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTheme(presentationData: PresentationData) {
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
|
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed() {
|
||||||
|
self.performAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var actionTemporarilyDisabled: Bool = false
|
||||||
|
|
||||||
|
func canBeHighlighted() -> Bool {
|
||||||
|
return self.isActionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIsHighlighted(isHighlighted: Bool) {
|
||||||
|
self.setIsHighlighted(isHighlighted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func performAction() {
|
||||||
|
if self.actionTemporarilyDisabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.actionTemporarilyDisabled = true
|
||||||
|
Queue.mainQueue().async { [weak self] in
|
||||||
|
self?.actionTemporarilyDisabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let controller = self.getController() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.item.action(controller, { [weak self] result in
|
||||||
|
self?.actionSelected(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActionEnabled: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIsHighlighted(_ value: Bool) {
|
||||||
|
if value {
|
||||||
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
} else {
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,486 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import TelegramUIPreferences
|
||||||
|
import TelegramStringFormatting
|
||||||
|
import ItemListUI
|
||||||
|
import PresentationDataUtils
|
||||||
|
import OverlayStatusController
|
||||||
|
import AccountContext
|
||||||
|
import ItemListPeerItem
|
||||||
|
import UndoUI
|
||||||
|
import ContextUI
|
||||||
|
import ItemListPeerActionItem
|
||||||
|
|
||||||
|
private enum StorageUsageExceptionsEntryTag: Hashable, ItemListItemTag {
|
||||||
|
case peer(EnginePeer.Id)
|
||||||
|
|
||||||
|
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
|
if let other = other as? StorageUsageExceptionsEntryTag, self == other {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class StorageUsageExceptionsScreenArguments {
|
||||||
|
let context: AccountContext
|
||||||
|
let openAddException: () -> Void
|
||||||
|
let openPeerMenu: (EnginePeer.Id, Int32) -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AccountContext,
|
||||||
|
openAddException: @escaping () -> Void,
|
||||||
|
openPeerMenu: @escaping (EnginePeer.Id, Int32) -> Void
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.openAddException = openAddException
|
||||||
|
self.openPeerMenu = openPeerMenu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum StorageUsageExceptionsSection: Int32 {
|
||||||
|
case add
|
||||||
|
case items
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum StorageUsageExceptionsEntry: ItemListNodeEntry {
|
||||||
|
enum SortIndex: Equatable, Comparable {
|
||||||
|
case index(Int)
|
||||||
|
case peer(index: Int, peerId: EnginePeer.Id)
|
||||||
|
|
||||||
|
static func <(lhs: SortIndex, rhs: SortIndex) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case let .index(index):
|
||||||
|
if case let .index(rhsIndex) = rhs {
|
||||||
|
return index < rhsIndex
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case let .peer(index, peerId):
|
||||||
|
if case let .peer(rhsIndex, rhsPeerId) = rhs {
|
||||||
|
if index != rhsIndex {
|
||||||
|
return index < rhsIndex
|
||||||
|
} else {
|
||||||
|
return peerId < rhsPeerId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StableId: Hashable {
|
||||||
|
case index(Int)
|
||||||
|
case peer(EnginePeer.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
case addException(String)
|
||||||
|
case exceptionsHeader(String)
|
||||||
|
case peer(index: Int, peer: FoundPeer, value: Int32)
|
||||||
|
|
||||||
|
var section: ItemListSectionId {
|
||||||
|
switch self {
|
||||||
|
case .addException:
|
||||||
|
return StorageUsageExceptionsSection.add.rawValue
|
||||||
|
case .exceptionsHeader, .peer:
|
||||||
|
return StorageUsageExceptionsSection.items.rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stableId: StableId {
|
||||||
|
switch self {
|
||||||
|
case .addException:
|
||||||
|
return .index(0)
|
||||||
|
case .exceptionsHeader:
|
||||||
|
return .index(1)
|
||||||
|
case let .peer(_, peer, _):
|
||||||
|
return .peer(peer.peer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortIndex: SortIndex {
|
||||||
|
switch self {
|
||||||
|
case .addException:
|
||||||
|
return .index(0)
|
||||||
|
case .exceptionsHeader:
|
||||||
|
return .index(1)
|
||||||
|
case let .peer(index, peer, _):
|
||||||
|
return .peer(index: index, peerId: peer.peer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: StorageUsageExceptionsEntry, rhs: StorageUsageExceptionsEntry) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case let .addException(text):
|
||||||
|
if case .addException(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .exceptionsHeader(text):
|
||||||
|
if case .exceptionsHeader(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .peer(index, peer, value):
|
||||||
|
if case .peer(index, peer, value) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func <(lhs: StorageUsageExceptionsEntry, rhs: StorageUsageExceptionsEntry) -> Bool {
|
||||||
|
return lhs.sortIndex < rhs.sortIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||||
|
let arguments = arguments as! StorageUsageExceptionsScreenArguments
|
||||||
|
switch self {
|
||||||
|
case let .addException(text):
|
||||||
|
let icon: UIImage? = PresentationResourcesItemList.createGroupIcon(presentationData.theme)
|
||||||
|
return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: text, alwaysPlain: false, sectionId: self.section, editing: false, action: {
|
||||||
|
arguments.openAddException()
|
||||||
|
})
|
||||||
|
case let .exceptionsHeader(text):
|
||||||
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
|
case let .peer(_, peer, value):
|
||||||
|
var additionalDetailLabel: String?
|
||||||
|
if let subscribers = peer.subscribers {
|
||||||
|
additionalDetailLabel = presentationData.strings.VoiceChat_Panel_Members(subscribers)
|
||||||
|
}
|
||||||
|
let optionText: String
|
||||||
|
if value == Int32.max {
|
||||||
|
optionText = presentationData.strings.ClearCache_Forever
|
||||||
|
} else {
|
||||||
|
optionText = timeIntervalString(strings: presentationData.strings, value: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, context: arguments.context, iconPeer: EnginePeer(peer.peer), title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: .firstLast), enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
||||||
|
arguments.openPeerMenu(peer.peer.id, value)
|
||||||
|
}, tag: StorageUsageExceptionsEntryTag.peer(peer.peer.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct StorageUsageExceptionsState: Equatable {
|
||||||
|
}
|
||||||
|
|
||||||
|
private func storageUsageExceptionsScreenEntries(
|
||||||
|
presentationData: PresentationData,
|
||||||
|
peerExceptions: [(peer: FoundPeer, value: Int32)],
|
||||||
|
state: StorageUsageExceptionsState
|
||||||
|
) -> [StorageUsageExceptionsEntry] {
|
||||||
|
var entries: [StorageUsageExceptionsEntry] = []
|
||||||
|
|
||||||
|
entries.append(.addException(presentationData.strings.Notification_Exceptions_AddException))
|
||||||
|
|
||||||
|
if !peerExceptions.isEmpty {
|
||||||
|
entries.append(.exceptionsHeader(presentationData.strings.Notifications_CategoryExceptions(Int32(peerExceptions.count)).uppercased()))
|
||||||
|
|
||||||
|
var index = 100
|
||||||
|
for item in peerExceptions {
|
||||||
|
entries.append(.peer(index: index, peer: item.peer, value: item.value))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
public func storageUsageExceptionsScreen(
|
||||||
|
context: AccountContext,
|
||||||
|
category: CacheStorageSettings.PeerStorageCategory,
|
||||||
|
isModal: Bool = false
|
||||||
|
) -> ViewController {
|
||||||
|
let statePromise = ValuePromise(StorageUsageExceptionsState())
|
||||||
|
let stateValue = Atomic(value: StorageUsageExceptionsState())
|
||||||
|
let updateState: ((StorageUsageExceptionsState) -> StorageUsageExceptionsState) -> Void = { f in
|
||||||
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
|
}
|
||||||
|
let _ = updateState
|
||||||
|
|
||||||
|
let cacheSettingsPromise = Promise<CacheStorageSettings>()
|
||||||
|
cacheSettingsPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings])
|
||||||
|
|> map { sharedData -> CacheStorageSettings in
|
||||||
|
let cacheSettings: CacheStorageSettings
|
||||||
|
if let value = sharedData.entries[SharedDataKeys.cacheStorageSettings]?.get(CacheStorageSettings.self) {
|
||||||
|
cacheSettings = value
|
||||||
|
} else {
|
||||||
|
cacheSettings = CacheStorageSettings.defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheSettings
|
||||||
|
})
|
||||||
|
|
||||||
|
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings]))
|
||||||
|
let accountSpecificSettings: Signal<AccountSpecificCacheStorageSettings, NoError> = context.account.postbox.combinedView(keys: [viewKey])
|
||||||
|
|> map { views -> AccountSpecificCacheStorageSettings in
|
||||||
|
let cacheSettings: AccountSpecificCacheStorageSettings
|
||||||
|
if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) {
|
||||||
|
cacheSettings = value
|
||||||
|
} else {
|
||||||
|
cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheSettings
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|
||||||
|
let peerExceptions: Signal<[(peer: FoundPeer, value: Int32)], NoError> = accountSpecificSettings
|
||||||
|
|> mapToSignal { accountSpecificSettings -> Signal<[(peer: FoundPeer, value: Int32)], NoError> in
|
||||||
|
return context.account.postbox.transaction { transaction -> [(peer: FoundPeer, value: Int32)] in
|
||||||
|
var result: [(peer: FoundPeer, value: Int32)] = []
|
||||||
|
|
||||||
|
for (peerId, value) in accountSpecificSettings.peerStorageTimeoutExceptions {
|
||||||
|
guard let peer = transaction.getPeer(peerId) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let peerCategory: CacheStorageSettings.PeerStorageCategory
|
||||||
|
var subscriberCount: Int32?
|
||||||
|
if peer is TelegramUser {
|
||||||
|
peerCategory = .privateChats
|
||||||
|
} else if peer is TelegramGroup {
|
||||||
|
peerCategory = .groups
|
||||||
|
|
||||||
|
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedGroupData {
|
||||||
|
subscriberCount = (cachedData.participants?.participants.count).flatMap(Int32.init)
|
||||||
|
}
|
||||||
|
} else if let channel = peer as? TelegramChannel {
|
||||||
|
if case .group = channel.info {
|
||||||
|
peerCategory = .groups
|
||||||
|
} else {
|
||||||
|
peerCategory = .channels
|
||||||
|
}
|
||||||
|
if peerCategory == category {
|
||||||
|
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData {
|
||||||
|
subscriberCount = cachedData.participantsSummary.memberCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerCategory != category {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append((peer: FoundPeer(peer: peer, subscribers: subscriberCount), value: value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.sorted(by: { lhs, rhs in
|
||||||
|
if lhs.value != rhs.value {
|
||||||
|
return lhs.value < rhs.value
|
||||||
|
}
|
||||||
|
return lhs.peer.peer.debugDisplayTitle < rhs.peer.peer.debugDisplayTitle
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)?
|
||||||
|
let _ = presentControllerImpl
|
||||||
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
|
|
||||||
|
var findPeerReferenceNode: ((EnginePeer.Id) -> ItemListDisclosureItemNode?)?
|
||||||
|
let _ = findPeerReferenceNode
|
||||||
|
|
||||||
|
var presentInGlobalOverlay: ((ViewController) -> Void)?
|
||||||
|
let _ = presentInGlobalOverlay
|
||||||
|
|
||||||
|
let actionDisposables = DisposableSet()
|
||||||
|
|
||||||
|
let clearDisposable = MetaDisposable()
|
||||||
|
actionDisposables.add(clearDisposable)
|
||||||
|
|
||||||
|
let arguments = StorageUsageExceptionsScreenArguments(
|
||||||
|
context: context,
|
||||||
|
openAddException: {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
var filter: ChatListNodePeersFilter = [.excludeRecent, .doNotSearchMessages, .removeSearchHeader]
|
||||||
|
switch category {
|
||||||
|
case .groups:
|
||||||
|
filter.insert(.onlyGroups)
|
||||||
|
case .privateChats:
|
||||||
|
filter.insert(.onlyPrivateChats)
|
||||||
|
filter.insert(.excludeSavedMessages)
|
||||||
|
filter.insert(.excludeSecretChats)
|
||||||
|
case .channels:
|
||||||
|
filter.insert(.onlyChannels)
|
||||||
|
}
|
||||||
|
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: filter, hasContactSelector: false, title: presentationData.strings.Notifications_AddExceptionTitle))
|
||||||
|
controller.peerSelected = { [weak controller] peer, _ in
|
||||||
|
let peerId = peer.id
|
||||||
|
|
||||||
|
let _ = updateAccountSpecificCacheStorageSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||||
|
var settings = settings
|
||||||
|
|
||||||
|
settings.peerStorageTimeoutExceptions[peerId] = Int32.max
|
||||||
|
|
||||||
|
return settings
|
||||||
|
}).start()
|
||||||
|
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
pushControllerImpl?(controller)
|
||||||
|
},
|
||||||
|
openPeerMenu: { peerId, currentValue in
|
||||||
|
let applyValue: (Int32?) -> Void = { value in
|
||||||
|
let _ = updateAccountSpecificCacheStorageSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||||
|
var settings = settings
|
||||||
|
|
||||||
|
if let value = value {
|
||||||
|
settings.peerStorageTimeoutExceptions[peerId] = value
|
||||||
|
} else {
|
||||||
|
settings.peerStorageTimeoutExceptions.removeValue(forKey: peerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings
|
||||||
|
}).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
var subItems: [ContextMenuItem] = []
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let presetValues: [Int32] = [
|
||||||
|
Int32.max,
|
||||||
|
31 * 24 * 60 * 60,
|
||||||
|
7 * 24 * 60 * 60,
|
||||||
|
1 * 24 * 60 * 60
|
||||||
|
]
|
||||||
|
|
||||||
|
for value in presetValues {
|
||||||
|
let optionText: String
|
||||||
|
if value == Int32.max {
|
||||||
|
optionText = presentationData.strings.ClearCache_Forever
|
||||||
|
} else {
|
||||||
|
optionText = timeIntervalString(strings: presentationData.strings, value: value)
|
||||||
|
}
|
||||||
|
subItems.append(.action(ContextMenuActionItem(text: optionText, icon: { theme in
|
||||||
|
if currentValue == value {
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, action: { _, f in
|
||||||
|
applyValue(value)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
subItems.append(.separator)
|
||||||
|
//TODO:localize
|
||||||
|
subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
applyValue(nil)
|
||||||
|
})))
|
||||||
|
|
||||||
|
if let sourceNode = findPeerReferenceNode?(peerId) {
|
||||||
|
let items: Signal<ContextController.Items, NoError> = .single(ContextController.Items(content: .list(subItems)))
|
||||||
|
let source: ContextContentSource = .reference(StorageUsageExceptionsContextReferenceContentSource(sourceView: sourceNode.labelNode.view))
|
||||||
|
|
||||||
|
let contextController = ContextController(
|
||||||
|
account: context.account,
|
||||||
|
presentationData: presentationData,
|
||||||
|
source: source,
|
||||||
|
items: items,
|
||||||
|
gesture: nil
|
||||||
|
)
|
||||||
|
sourceNode.updateHasContextMenu(hasContextMenu: true)
|
||||||
|
contextController.dismissed = { [weak sourceNode] in
|
||||||
|
sourceNode?.updateHasContextMenu(hasContextMenu: false)
|
||||||
|
}
|
||||||
|
presentInGlobalOverlay?(contextController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let _ = cacheSettingsPromise
|
||||||
|
|
||||||
|
var dismissImpl: (() -> Void)?
|
||||||
|
|
||||||
|
let signal = combineLatest(queue: .mainQueue(),
|
||||||
|
context.sharedContext.presentationData,
|
||||||
|
peerExceptions,
|
||||||
|
statePromise.get()
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|> map { presentationData, peerExceptions, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
|
let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||||
|
dismissImpl?()
|
||||||
|
}) : nil
|
||||||
|
|
||||||
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Notifications_ExceptionsTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||||
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: storageUsageExceptionsScreenEntries(presentationData: presentationData, peerExceptions: peerExceptions, state: state), style: .blocks, emptyStateItem: nil, animateChanges: false)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
|
}
|
||||||
|
|> afterDisposed {
|
||||||
|
actionDisposables.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = ItemListController(context: context, state: signal)
|
||||||
|
if isModal {
|
||||||
|
controller.navigationPresentation = .modal
|
||||||
|
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
|
}
|
||||||
|
presentControllerImpl = { [weak controller] c, contextType, a in
|
||||||
|
controller?.present(c, in: contextType, with: a)
|
||||||
|
}
|
||||||
|
pushControllerImpl = { [weak controller] c in
|
||||||
|
controller?.push(c)
|
||||||
|
}
|
||||||
|
presentInGlobalOverlay = { [weak controller] c in
|
||||||
|
controller?.presentInGlobalOverlay(c, with: nil)
|
||||||
|
}
|
||||||
|
findPeerReferenceNode = { [weak controller] peerId in
|
||||||
|
guard let controller else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetTag: StorageUsageExceptionsEntryTag = .peer(peerId)
|
||||||
|
var resultItemNode: ItemListItemNode?
|
||||||
|
controller.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? ItemListItemNode {
|
||||||
|
if let tag = itemNode.tag, tag.isEqual(to: targetTag) {
|
||||||
|
resultItemNode = itemNode
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let resultItemNode = resultItemNode as? ItemListDisclosureItemNode {
|
||||||
|
return resultItemNode
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dismissImpl = { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class StorageUsageExceptionsContextReferenceContentSource: ContextReferenceContentSource {
|
||||||
|
private let sourceView: UIView
|
||||||
|
|
||||||
|
init(sourceView: UIView) {
|
||||||
|
self.sourceView = sourceView
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||||
|
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -896,6 +896,7 @@ public class Account {
|
|||||||
private let managedOperationsDisposable = DisposableSet()
|
private let managedOperationsDisposable = DisposableSet()
|
||||||
private let managedTopReactionsDisposable = MetaDisposable()
|
private let managedTopReactionsDisposable = MetaDisposable()
|
||||||
private var storageSettingsDisposable: Disposable?
|
private var storageSettingsDisposable: Disposable?
|
||||||
|
private var automaticCacheEvictionContext: AutomaticCacheEvictionContext?
|
||||||
|
|
||||||
public let importableContacts = Promise<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]>()
|
public let importableContacts = Promise<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]>()
|
||||||
|
|
||||||
@ -1190,13 +1191,15 @@ public class Account {
|
|||||||
|
|
||||||
if !supplementary {
|
if !supplementary {
|
||||||
let mediaBox = postbox.mediaBox
|
let mediaBox = postbox.mediaBox
|
||||||
self.storageSettingsDisposable = accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings]).start(next: { [weak mediaBox] sharedData in
|
/*self.storageSettingsDisposable = accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings]).start(next: { [weak mediaBox] sharedData in
|
||||||
guard let mediaBox = mediaBox else {
|
guard let mediaBox = mediaBox else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let settings: CacheStorageSettings = sharedData.entries[SharedDataKeys.cacheStorageSettings]?.get(CacheStorageSettings.self) ?? CacheStorageSettings.defaultSettings
|
let settings: CacheStorageSettings = sharedData.entries[SharedDataKeys.cacheStorageSettings]?.get(CacheStorageSettings.self) ?? CacheStorageSettings.defaultSettings
|
||||||
mediaBox.setMaxStoreTimes(general: settings.defaultCacheStorageTimeout, shortLived: 60 * 60, gigabytesLimit: settings.defaultCacheStorageLimitGigabytes)
|
mediaBox.setMaxStoreTimes(general: settings.defaultCacheStorageTimeout, shortLived: 60 * 60, gigabytesLimit: settings.defaultCacheStorageLimitGigabytes)
|
||||||
})
|
})*/
|
||||||
|
|
||||||
|
mediaBox.setMaxStoreTimes(general: 1 * 24 * 60 * 60, shortLived: 60 * 60, gigabytesLimit: 100 * 1024 * 1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = masterNotificationsKey(masterNotificationKeyValue: self.masterNotificationKey, postbox: self.postbox, ignoreDisabled: false, createIfNotExists: true).start(next: { key in
|
let _ = masterNotificationsKey(masterNotificationKeyValue: self.masterNotificationKey, postbox: self.postbox, ignoreDisabled: false, createIfNotExists: true).start(next: { key in
|
||||||
@ -1218,6 +1221,8 @@ public class Account {
|
|||||||
strongSelf.managedTopReactionsDisposable.set(managedTopReactions(postbox: strongSelf.postbox, network: strongSelf.network).start())
|
strongSelf.managedTopReactionsDisposable.set(managedTopReactions(postbox: strongSelf.postbox, network: strongSelf.network).start())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.automaticCacheEvictionContext = AutomaticCacheEvictionContext(postbox: postbox, accountManager: accountManager)
|
||||||
|
|
||||||
/*#if DEBUG
|
/*#if DEBUG
|
||||||
self.managedOperationsDisposable.add(debugFetchAllStickers(account: self).start(completed: {
|
self.managedOperationsDisposable.add(debugFetchAllStickers(account: self).start(completed: {
|
||||||
print("debugFetchAllStickers done")
|
print("debugFetchAllStickers done")
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
|
||||||
public func updateCacheStorageSettingsInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (CacheStorageSettings) -> CacheStorageSettings) -> Signal<Void, NoError> {
|
public func updateCacheStorageSettingsInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (CacheStorageSettings) -> CacheStorageSettings) -> Signal<Void, NoError> {
|
||||||
return accountManager.transaction { transaction -> Void in
|
return accountManager.transaction { transaction -> Void in
|
||||||
transaction.updateSharedData(SharedDataKeys.cacheStorageSettings, { entry in
|
transaction.updateSharedData(SharedDataKeys.cacheStorageSettings, { entry in
|
||||||
@ -16,3 +15,17 @@ public func updateCacheStorageSettingsInteractively(accountManager: AccountManag
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func updateAccountSpecificCacheStorageSettingsInteractively(postbox: Postbox, _ f: @escaping (AccountSpecificCacheStorageSettings) -> AccountSpecificCacheStorageSettings) -> Signal<Void, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Void in
|
||||||
|
transaction.updatePreferencesEntry(key: PreferencesKeys.accountSpecificCacheStorageSettings, { entry in
|
||||||
|
let currentSettings: AccountSpecificCacheStorageSettings
|
||||||
|
if let entry = entry?.get(AccountSpecificCacheStorageSettings.self) {
|
||||||
|
currentSettings = entry
|
||||||
|
} else {
|
||||||
|
currentSettings = AccountSpecificCacheStorageSettings.defaultSettings
|
||||||
|
}
|
||||||
|
return PreferencesEntry(f(currentSettings))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3461,7 +3461,7 @@ func replayFinalState(
|
|||||||
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
||||||
})
|
})
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = mediaBox.removeCachedResources(Set(resourceIds), force: true).start()
|
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start()
|
||||||
}
|
}
|
||||||
deletedMessageIds.append(contentsOf: ids.map { .global($0) })
|
deletedMessageIds.append(contentsOf: ids.map { .global($0) })
|
||||||
case let .DeleteMessages(ids):
|
case let .DeleteMessages(ids):
|
||||||
@ -3478,7 +3478,7 @@ func replayFinalState(
|
|||||||
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
||||||
})
|
})
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = mediaBox.removeCachedResources(Set(resourceIds), force: true).start()
|
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start()
|
||||||
}
|
}
|
||||||
case let .UpdatePeerChatInclusion(peerId, groupId, changedGroup):
|
case let .UpdatePeerChatInclusion(peerId, groupId, changedGroup):
|
||||||
let currentInclusion = transaction.getPeerChatListInclusion(peerId)
|
let currentInclusion = transaction.getPeerChatListInclusion(peerId)
|
||||||
|
|||||||
@ -1,16 +1,43 @@
|
|||||||
|
import Foundation
|
||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
public struct CacheStorageSettings: Codable, Equatable {
|
public struct CacheStorageSettings: Codable, Equatable {
|
||||||
public let defaultCacheStorageTimeout: Int32
|
public enum PeerStorageCategory: String, Codable, Hashable {
|
||||||
public let defaultCacheStorageLimitGigabytes: Int32
|
case privateChats = "privateChats"
|
||||||
|
case groups = "groups"
|
||||||
public static var defaultSettings: CacheStorageSettings {
|
case channels = "channels"
|
||||||
return CacheStorageSettings(defaultCacheStorageTimeout: Int32.max, defaultCacheStorageLimitGigabytes: 8 * 1024 * 1024)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(defaultCacheStorageTimeout: Int32, defaultCacheStorageLimitGigabytes: Int32) {
|
private struct CategoryStorageTimeoutRepresentation: Codable {
|
||||||
|
var key: PeerStorageCategory
|
||||||
|
var value: Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
public var defaultCacheStorageTimeout: Int32
|
||||||
|
public var defaultCacheStorageLimitGigabytes: Int32
|
||||||
|
|
||||||
|
public var categoryStorageTimeout: [PeerStorageCategory: Int32]
|
||||||
|
|
||||||
|
public static var defaultSettings: CacheStorageSettings {
|
||||||
|
return CacheStorageSettings(
|
||||||
|
defaultCacheStorageTimeout: Int32.max,
|
||||||
|
defaultCacheStorageLimitGigabytes: 8 * 1024 * 1024,
|
||||||
|
categoryStorageTimeout: [
|
||||||
|
.privateChats: Int32.max,
|
||||||
|
.groups: Int32.max,
|
||||||
|
.channels: Int32(1 * 24 * 60 * 60)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
defaultCacheStorageTimeout: Int32,
|
||||||
|
defaultCacheStorageLimitGigabytes: Int32,
|
||||||
|
categoryStorageTimeout: [PeerStorageCategory: Int32]
|
||||||
|
) {
|
||||||
self.defaultCacheStorageTimeout = defaultCacheStorageTimeout
|
self.defaultCacheStorageTimeout = defaultCacheStorageTimeout
|
||||||
self.defaultCacheStorageLimitGigabytes = defaultCacheStorageLimitGigabytes
|
self.defaultCacheStorageLimitGigabytes = defaultCacheStorageLimitGigabytes
|
||||||
|
self.categoryStorageTimeout = categoryStorageTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -25,6 +52,20 @@ public struct CacheStorageSettings: Codable, Equatable {
|
|||||||
} else {
|
} else {
|
||||||
self.defaultCacheStorageLimitGigabytes = 8 * 1024 * 1024
|
self.defaultCacheStorageLimitGigabytes = 8 * 1024 * 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let data = try container.decodeIfPresent(Data.self, forKey: "categoryStorageTimeoutJson") {
|
||||||
|
if let items = try? JSONDecoder().decode([CategoryStorageTimeoutRepresentation].self, from: data) {
|
||||||
|
var categoryStorageTimeout: [PeerStorageCategory: Int32] = [:]
|
||||||
|
for item in items {
|
||||||
|
categoryStorageTimeout[item.key] = item.value
|
||||||
|
}
|
||||||
|
self.categoryStorageTimeout = categoryStorageTimeout
|
||||||
|
} else {
|
||||||
|
self.categoryStorageTimeout = CacheStorageSettings.defaultSettings.categoryStorageTimeout
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.categoryStorageTimeout = CacheStorageSettings.defaultSettings.categoryStorageTimeout
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -32,12 +73,64 @@ public struct CacheStorageSettings: Codable, Equatable {
|
|||||||
|
|
||||||
try container.encode(self.defaultCacheStorageTimeout, forKey: "dt")
|
try container.encode(self.defaultCacheStorageTimeout, forKey: "dt")
|
||||||
try container.encode(self.defaultCacheStorageLimitGigabytes, forKey: "dl")
|
try container.encode(self.defaultCacheStorageLimitGigabytes, forKey: "dl")
|
||||||
}
|
|
||||||
|
|
||||||
public func withUpdatedDefaultCacheStorageTimeout(_ defaultCacheStorageTimeout: Int32) -> CacheStorageSettings {
|
var categoryStorageTimeoutValues: [CategoryStorageTimeoutRepresentation] = []
|
||||||
return CacheStorageSettings(defaultCacheStorageTimeout: defaultCacheStorageTimeout, defaultCacheStorageLimitGigabytes: self.defaultCacheStorageLimitGigabytes)
|
for (key, value) in self.categoryStorageTimeout {
|
||||||
}
|
categoryStorageTimeoutValues.append(CategoryStorageTimeoutRepresentation(key: key, value: value))
|
||||||
public func withUpdatedDefaultCacheStorageLimitGigabytes(_ defaultCacheStorageLimitGigabytes: Int32) -> CacheStorageSettings {
|
}
|
||||||
return CacheStorageSettings(defaultCacheStorageTimeout: self.defaultCacheStorageTimeout, defaultCacheStorageLimitGigabytes: defaultCacheStorageLimitGigabytes)
|
if let data = try? JSONEncoder().encode(categoryStorageTimeoutValues) {
|
||||||
|
try container.encode(data, forKey: "categoryStorageTimeoutJson")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AccountSpecificCacheStorageSettings: Codable, Equatable {
|
||||||
|
private struct PeerStorageTimeoutExceptionRepresentation: Codable {
|
||||||
|
var key: PeerId
|
||||||
|
var value: Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
public var peerStorageTimeoutExceptions: [PeerId: Int32]
|
||||||
|
|
||||||
|
public static var defaultSettings: AccountSpecificCacheStorageSettings {
|
||||||
|
return AccountSpecificCacheStorageSettings(
|
||||||
|
peerStorageTimeoutExceptions: [:]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
peerStorageTimeoutExceptions: [PeerId: Int32]
|
||||||
|
) {
|
||||||
|
self.peerStorageTimeoutExceptions = peerStorageTimeoutExceptions
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
|
if let data = try container.decodeIfPresent(Data.self, forKey: "peerStorageTimeoutExceptionsJson") {
|
||||||
|
if let items = try? JSONDecoder().decode([PeerStorageTimeoutExceptionRepresentation].self, from: data) {
|
||||||
|
var peerStorageTimeoutExceptions: [PeerId: Int32] = [:]
|
||||||
|
for item in items {
|
||||||
|
peerStorageTimeoutExceptions[item.key] = item.value
|
||||||
|
}
|
||||||
|
self.peerStorageTimeoutExceptions = peerStorageTimeoutExceptions
|
||||||
|
} else {
|
||||||
|
self.peerStorageTimeoutExceptions = AccountSpecificCacheStorageSettings.defaultSettings.peerStorageTimeoutExceptions
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.peerStorageTimeoutExceptions = AccountSpecificCacheStorageSettings.defaultSettings.peerStorageTimeoutExceptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
|
var peerStorageTimeoutExceptionsValues: [PeerStorageTimeoutExceptionRepresentation] = []
|
||||||
|
for (key, value) in self.peerStorageTimeoutExceptions {
|
||||||
|
peerStorageTimeoutExceptionsValues.append(PeerStorageTimeoutExceptionRepresentation(key: key, value: value))
|
||||||
|
}
|
||||||
|
if let data = try? JSONEncoder().encode(peerStorageTimeoutExceptionsValues) {
|
||||||
|
try container.encode(data, forKey: "peerStorageTimeoutExceptionsJson")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -247,6 +247,7 @@ private enum PreferencesKeyValues: Int32 {
|
|||||||
case reactionSettings = 24
|
case reactionSettings = 24
|
||||||
case premiumPromo = 26
|
case premiumPromo = 26
|
||||||
case globalMessageAutoremoveTimeoutSettings = 27
|
case globalMessageAutoremoveTimeoutSettings = 27
|
||||||
|
case accountSpecificCacheStorageSettings = 28
|
||||||
}
|
}
|
||||||
|
|
||||||
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
||||||
@ -381,6 +382,12 @@ public struct PreferencesKeys {
|
|||||||
key.setInt32(0, value: PreferencesKeyValues.globalMessageAutoremoveTimeoutSettings.rawValue)
|
key.setInt32(0, value: PreferencesKeyValues.globalMessageAutoremoveTimeoutSettings.rawValue)
|
||||||
return key
|
return key
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
public static let accountSpecificCacheStorageSettings: ValueBoxKey = {
|
||||||
|
let key = ValueBoxKey(length: 4)
|
||||||
|
key.setInt32(0, value: PreferencesKeyValues.accountSpecificCacheStorageSettings.rawValue)
|
||||||
|
return key
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SharedDataKeyValues: Int32 {
|
private enum SharedDataKeyValues: Int32 {
|
||||||
|
|||||||
@ -34,7 +34,7 @@ public func _internal_deleteMessages(transaction: Transaction, mediaBox: MediaBo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = mediaBox.removeCachedResources(Set(resourceIds), force: true).start()
|
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start()
|
||||||
}
|
}
|
||||||
for id in ids {
|
for id in ids {
|
||||||
if id.peerId.namespace == Namespaces.Peer.CloudChannel && id.namespace == Namespaces.Message.Cloud {
|
if id.peerId.namespace == Namespaces.Peer.CloudChannel && id.namespace == Namespaces.Message.Cloud {
|
||||||
@ -62,7 +62,7 @@ func _internal_deleteAllMessagesWithAuthor(transaction: Transaction, mediaBox: M
|
|||||||
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
||||||
})
|
})
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = mediaBox.removeCachedResources(Set(resourceIds)).start()
|
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds))).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ func _internal_deleteAllMessagesWithForwardAuthor(transaction: Transaction, medi
|
|||||||
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
||||||
})
|
})
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = mediaBox.removeCachedResources(Set(resourceIds), force: true).start()
|
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ func _internal_clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = mediaBox.removeCachedResources(Set(resourceIds), force: true).start()
|
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transaction.clearHistory(peerId, threadId: threadId, minTimestamp: nil, maxTimestamp: nil, namespaces: namespaces, forEachMedia: { _ in
|
transaction.clearHistory(peerId, threadId: threadId, minTimestamp: nil, maxTimestamp: nil, namespaces: namespaces, forEachMedia: { _ in
|
||||||
@ -101,7 +101,7 @@ func _internal_clearHistoryInRange(transaction: Transaction, mediaBox: MediaBox,
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = mediaBox.removeCachedResources(Set(resourceIds), force: true).start()
|
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transaction.clearHistory(peerId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, namespaces: namespaces, forEachMedia: { _ in
|
transaction.clearHistory(peerId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, namespaces: namespaces, forEachMedia: { _ in
|
||||||
|
|||||||
@ -108,7 +108,7 @@ func managedApplyPendingScheduledMessagesActions(postbox: Postbox, network: Netw
|
|||||||
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
||||||
})
|
})
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = postbox.mediaBox.removeCachedResources(Set(resourceIds)).start()
|
let _ = postbox.mediaBox.removeCachedResources(Array(Set(resourceIds))).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
|
|||||||
@ -665,7 +665,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
|||||||
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
||||||
})
|
})
|
||||||
if !resourceIds.isEmpty {
|
if !resourceIds.isEmpty {
|
||||||
let _ = postbox.mediaBox.removeCachedResources(Set(resourceIds)).start()
|
let _ = postbox.mediaBox.removeCachedResources(Array(Set(resourceIds))).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .chatFull:
|
case .chatFull:
|
||||||
|
|||||||
@ -81,7 +81,7 @@ func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, a
|
|||||||
totalSize += resourceSize
|
totalSize += resourceSize
|
||||||
|
|
||||||
for reference in entry.references {
|
for reference in entry.references {
|
||||||
if let message = transaction.getMessage(MessageId(peerId: reference.peerId, namespace: MessageId.Namespace(reference.messageNamespace), id: reference.messageId)) {
|
if let message = transaction.getMessage(MessageId(peerId: PeerId(reference.peerId), namespace: MessageId.Namespace(reference.messageNamespace), id: reference.messageId)) {
|
||||||
for mediaItem in message.media {
|
for mediaItem in message.media {
|
||||||
guard let mediaId = mediaItem.id else {
|
guard let mediaId = mediaItem.id else {
|
||||||
continue
|
continue
|
||||||
@ -102,7 +102,7 @@ func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, a
|
|||||||
mediaSize += resourceSize
|
mediaSize += resourceSize
|
||||||
processedResourceIds.insert(resourceId.stringRepresentation)
|
processedResourceIds.insert(resourceId.stringRepresentation)
|
||||||
|
|
||||||
media[reference.peerId, default: [:]][category, default: [:]][mediaId, default: 0] += resourceSize
|
media[PeerId(reference.peerId), default: [:]][category, default: [:]][mediaId, default: 0] += resourceSize
|
||||||
if let index = mediaResourceIds.index(forKey: mediaId) {
|
if let index = mediaResourceIds.index(forKey: mediaId) {
|
||||||
if !mediaResourceIds[index].value.contains(resourceId) {
|
if !mediaResourceIds[index].value.contains(resourceId) {
|
||||||
mediaResourceIds[mediaId]?.append(resourceId)
|
mediaResourceIds[mediaId]?.append(resourceId)
|
||||||
@ -489,5 +489,5 @@ func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, a
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _internal_clearCachedMediaResources(account: Account, mediaResourceIds: Set<MediaResourceId>) -> Signal<Float, NoError> {
|
func _internal_clearCachedMediaResources(account: Account, mediaResourceIds: Set<MediaResourceId>) -> Signal<Float, NoError> {
|
||||||
return account.postbox.mediaBox.removeCachedResources(mediaResourceIds)
|
return account.postbox.mediaBox.removeCachedResources(Array(mediaResourceIds))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,202 @@
|
|||||||
|
import Foundation
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
|
||||||
|
final class AutomaticCacheEvictionContext {
|
||||||
|
private final class Impl {
|
||||||
|
private struct CombinedSettings: Equatable {
|
||||||
|
var categoryStorageTimeout: [CacheStorageSettings.PeerStorageCategory: Int32]
|
||||||
|
var exceptions: [PeerId: Int32]
|
||||||
|
}
|
||||||
|
|
||||||
|
let queue: Queue
|
||||||
|
let processingQueue: Queue
|
||||||
|
let accountManager: AccountManager<TelegramAccountManagerTypes>
|
||||||
|
let postbox: Postbox
|
||||||
|
|
||||||
|
var settingsDisposable: Disposable?
|
||||||
|
var processDisposable: Disposable?
|
||||||
|
|
||||||
|
init(queue: Queue, accountManager: AccountManager<TelegramAccountManagerTypes>, postbox: Postbox) {
|
||||||
|
self.queue = queue
|
||||||
|
self.processingQueue = Queue(name: "AutomaticCacheEviction-Processing", qos: .background)
|
||||||
|
self.accountManager = accountManager
|
||||||
|
self.postbox = postbox
|
||||||
|
|
||||||
|
self.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.settingsDisposable?.dispose()
|
||||||
|
self.processDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
self.settingsDisposable?.dispose()
|
||||||
|
self.processDisposable?.dispose()
|
||||||
|
|
||||||
|
let cacheSettings = self.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings])
|
||||||
|
|> map { sharedData -> CacheStorageSettings in
|
||||||
|
let cacheSettings: CacheStorageSettings
|
||||||
|
if let value = sharedData.entries[SharedDataKeys.cacheStorageSettings]?.get(CacheStorageSettings.self) {
|
||||||
|
cacheSettings = value
|
||||||
|
} else {
|
||||||
|
cacheSettings = CacheStorageSettings.defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings]))
|
||||||
|
let accountSpecificSettings = self.postbox.combinedView(keys: [viewKey])
|
||||||
|
|> map { views -> AccountSpecificCacheStorageSettings in
|
||||||
|
let cacheSettings: AccountSpecificCacheStorageSettings
|
||||||
|
if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) {
|
||||||
|
cacheSettings = value
|
||||||
|
} else {
|
||||||
|
cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
self.settingsDisposable = (combineLatest(queue: self.queue,
|
||||||
|
cacheSettings,
|
||||||
|
accountSpecificSettings
|
||||||
|
)
|
||||||
|
|> map { cacheSettings, accountSpecificSettings -> CombinedSettings in
|
||||||
|
return CombinedSettings(
|
||||||
|
categoryStorageTimeout: cacheSettings.categoryStorageTimeout,
|
||||||
|
exceptions: accountSpecificSettings.peerStorageTimeoutExceptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|> deliverOn(self.queue)).start(next: { [weak self] combinedSettings in
|
||||||
|
self?.restart(settings: combinedSettings)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func restart(settings: CombinedSettings) {
|
||||||
|
self.processDisposable?.dispose()
|
||||||
|
|
||||||
|
let processingQueue = self.processingQueue
|
||||||
|
let postbox = self.postbox
|
||||||
|
let mediaBox = self.postbox.mediaBox
|
||||||
|
|
||||||
|
let _ = processingQueue
|
||||||
|
let _ = mediaBox
|
||||||
|
|
||||||
|
self.processDisposable = (self.postbox.mediaBox.storageBox.allPeerIds()
|
||||||
|
|> mapToSignal { peerIds -> Signal<Never, NoError> in
|
||||||
|
return postbox.transaction { transaction -> [PeerId: CacheStorageSettings.PeerStorageCategory] in
|
||||||
|
var channelCategoryMapping: [PeerId: CacheStorageSettings.PeerStorageCategory] = [:]
|
||||||
|
for peerId in peerIds {
|
||||||
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
var category: CacheStorageSettings.PeerStorageCategory = .channels
|
||||||
|
if let peer = transaction.getPeer(peerId) as? TelegramChannel, case .group = peer.info {
|
||||||
|
category = .groups
|
||||||
|
}
|
||||||
|
channelCategoryMapping[peerId] = category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return channelCategoryMapping
|
||||||
|
}
|
||||||
|
|> mapToSignal { channelCategoryMapping -> Signal<Never, NoError> in
|
||||||
|
var signals: Signal<Never, NoError> = .complete()
|
||||||
|
|
||||||
|
var matchingPeers = 0
|
||||||
|
|
||||||
|
for peerId in peerIds {
|
||||||
|
let timeout: Int32
|
||||||
|
if let value = settings.exceptions[peerId] {
|
||||||
|
timeout = value
|
||||||
|
} else {
|
||||||
|
switch peerId.namespace {
|
||||||
|
case Namespaces.Peer.CloudUser, Namespaces.Peer.SecretChat:
|
||||||
|
timeout = settings.categoryStorageTimeout[.privateChats] ?? Int32.max
|
||||||
|
case Namespaces.Peer.CloudGroup:
|
||||||
|
timeout = settings.categoryStorageTimeout[.groups] ?? Int32.max
|
||||||
|
default:
|
||||||
|
if let category = channelCategoryMapping[peerId], case .groups = category {
|
||||||
|
timeout = settings.categoryStorageTimeout[.groups] ?? Int32.max
|
||||||
|
} else {
|
||||||
|
timeout = settings.categoryStorageTimeout[.channels] ?? Int32.max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeout == Int32.max {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matchingPeers += 1
|
||||||
|
|
||||||
|
let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - timeout
|
||||||
|
//let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||||
|
|
||||||
|
signals = signals |> then(mediaBox.storageBox.all(peerId: peerId)
|
||||||
|
|> mapToSignal { peerResourceIds -> Signal<Never, NoError> in
|
||||||
|
return Signal { subscriber in
|
||||||
|
var isCancelled = false
|
||||||
|
|
||||||
|
processingQueue.justDispatch {
|
||||||
|
var removeIds: [MediaResourceId] = []
|
||||||
|
var removeRawIds: [Data] = []
|
||||||
|
var localCounter = 0
|
||||||
|
for resourceId in peerResourceIds {
|
||||||
|
localCounter += 1
|
||||||
|
if localCounter % 100 == 0 {
|
||||||
|
if isCancelled {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeRawIds.append(resourceId)
|
||||||
|
let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!)
|
||||||
|
let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id)
|
||||||
|
if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp {
|
||||||
|
removeIds.append(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !removeIds.isEmpty {
|
||||||
|
Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources")
|
||||||
|
|
||||||
|
let _ = mediaBox.removeCachedResources(removeIds).start(completed: {
|
||||||
|
mediaBox.storageBox.remove(ids: removeRawIds)
|
||||||
|
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
isCancelled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.shared.log("AutomaticCacheEviction", "have \(matchingPeers) peers with data")
|
||||||
|
|
||||||
|
return signals
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let queue: Queue
|
||||||
|
private let impl: QueueLocalObject<Impl>
|
||||||
|
|
||||||
|
init(postbox: Postbox, accountManager: AccountManager<TelegramAccountManagerTypes>) {
|
||||||
|
let queue = Queue(name: "AutomaticCacheEviction")
|
||||||
|
self.queue = queue
|
||||||
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
|
return Impl(queue: queue, accountManager: accountManager, postbox: postbox)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15343,7 +15343,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var attemptSelectionImpl: ((Peer) -> Void)?
|
var attemptSelectionImpl: ((Peer) -> Void)?
|
||||||
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, filter: filter, attemptSelection: { peer, _ in
|
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, filter: filter, attemptSelection: { peer, _ in
|
||||||
attemptSelectionImpl?(peer)
|
attemptSelectionImpl?(peer)
|
||||||
}, multipleSelection: true, forwardedMessageIds: messages.map { $0.id }))
|
}, multipleSelection: true, forwardedMessageIds: messages.map { $0.id }, selectForumThreads: true))
|
||||||
let context = self.context
|
let context = self.context
|
||||||
attemptSelectionImpl = { [weak self, weak controller] peer in
|
attemptSelectionImpl = { [weak self, weak controller] peer in
|
||||||
guard let strongSelf = self, let controller = controller else {
|
guard let strongSelf = self, let controller = controller else {
|
||||||
@ -15700,7 +15700,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
break
|
break
|
||||||
case let .chat(textInputState, _, _):
|
case let .chat(textInputState, _, _):
|
||||||
if let textInputState = textInputState {
|
if let textInputState = textInputState {
|
||||||
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData))
|
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, selectForumThreads: true))
|
||||||
controller.peerSelected = { [weak self, weak controller] peer, threadId in
|
controller.peerSelected = { [weak self, weak controller] peer, threadId in
|
||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
|
|
||||||
|
|||||||
@ -713,7 +713,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled]))
|
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled], selectForumThreads: true))
|
||||||
controller.peerSelected = { [weak controller] peer, _ in
|
controller.peerSelected = { [weak controller] peer, _ in
|
||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
|
|
||||||
|
|||||||
@ -19,11 +19,16 @@ private final class MultiScaleTextStateNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class MultiScaleTextState {
|
final class MultiScaleTextState {
|
||||||
let attributedText: NSAttributedString
|
struct Attributes {
|
||||||
|
var font: UIFont
|
||||||
|
var color: UIColor
|
||||||
|
}
|
||||||
|
|
||||||
|
let attributes: Attributes
|
||||||
let constrainedSize: CGSize
|
let constrainedSize: CGSize
|
||||||
|
|
||||||
init(attributedText: NSAttributedString, constrainedSize: CGSize) {
|
init(attributes: Attributes, constrainedSize: CGSize) {
|
||||||
self.attributedText = attributedText
|
self.attributes = attributes
|
||||||
self.constrainedSize = constrainedSize
|
self.constrainedSize = constrainedSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,7 +54,7 @@ final class MultiScaleTextNode: ASDisplayNode {
|
|||||||
return self.stateNodes[key]?.textNode
|
return self.stateNodes[key]?.textNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(states: [AnyHashable: MultiScaleTextState], mainState: AnyHashable) -> [AnyHashable: MultiScaleTextLayout] {
|
func updateLayout(text: String, states: [AnyHashable: MultiScaleTextState], mainState: AnyHashable) -> [AnyHashable: MultiScaleTextLayout] {
|
||||||
assert(Set(states.keys) == Set(self.stateNodes.keys))
|
assert(Set(states.keys) == Set(self.stateNodes.keys))
|
||||||
assert(states[mainState] != nil)
|
assert(states[mainState] != nil)
|
||||||
|
|
||||||
@ -57,7 +62,7 @@ final class MultiScaleTextNode: ASDisplayNode {
|
|||||||
var mainLayout: MultiScaleTextLayout?
|
var mainLayout: MultiScaleTextLayout?
|
||||||
for (key, state) in states {
|
for (key, state) in states {
|
||||||
if let node = self.stateNodes[key] {
|
if let node = self.stateNodes[key] {
|
||||||
node.textNode.attributedText = state.attributedText
|
node.textNode.attributedText = NSAttributedString(string: text, font: state.attributes.font, textColor: state.attributes.color)
|
||||||
let nodeSize = node.textNode.updateLayout(state.constrainedSize)
|
let nodeSize = node.textNode.updateLayout(state.constrainedSize)
|
||||||
let nodeLayout = MultiScaleTextLayout(size: nodeSize)
|
let nodeLayout = MultiScaleTextLayout(size: nodeSize)
|
||||||
if key == mainState {
|
if key == mainState {
|
||||||
|
|||||||
@ -72,7 +72,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
case let .botStart(peer, payload):
|
case let .botStart(peer, payload):
|
||||||
openPeer(EnginePeer(peer), .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)))
|
openPeer(EnginePeer(peer), .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)))
|
||||||
case let .groupBotStart(botPeerId, payload, adminRights):
|
case let .groupBotStart(botPeerId, payload, adminRights):
|
||||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages], hasContactSelector: false, title: presentationData.strings.Bot_AddToChat_Title))
|
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages], hasContactSelector: false, title: presentationData.strings.Bot_AddToChat_Title, selectForumThreads: true))
|
||||||
controller.peerSelected = { [weak controller] peer, _ in
|
controller.peerSelected = { [weak controller] peer, _ in
|
||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
|
|
||||||
@ -322,7 +322,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
present(shareController, nil)
|
present(shareController, nil)
|
||||||
context.sharedContext.applicationBindings.dismissNativeController()
|
context.sharedContext.applicationBindings.dismissNativeController()
|
||||||
} else {
|
} else {
|
||||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled]))
|
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled], selectForumThreads: true))
|
||||||
controller.peerSelected = { [weak controller] peer, _ in
|
controller.peerSelected = { [weak controller] peer, _ in
|
||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
|
|
||||||
@ -604,7 +604,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let navigationController = navigationController {
|
if let navigationController = navigationController {
|
||||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, filter: filters, hasChatListSelector: true, hasContactSelector: false, title: presentationData.strings.WebApp_SelectChat))
|
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, filter: filters, hasChatListSelector: true, hasContactSelector: false, title: presentationData.strings.WebApp_SelectChat, selectForumThreads: true))
|
||||||
controller.peerSelected = { [weak navigationController] peer, _ in
|
controller.peerSelected = { [weak navigationController] peer, _ in
|
||||||
guard let navigationController else {
|
guard let navigationController else {
|
||||||
return
|
return
|
||||||
@ -656,7 +656,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let navigationController = navigationController {
|
if let navigationController = navigationController {
|
||||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, filter: filters, hasChatListSelector: true, hasContactSelector: false, title: presentationData.strings.WebApp_SelectChat))
|
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, filter: filters, hasChatListSelector: true, hasContactSelector: false, title: presentationData.strings.WebApp_SelectChat, selectForumThreads: true))
|
||||||
controller.peerSelected = { [weak navigationController] peer, _ in
|
controller.peerSelected = { [weak navigationController] peer, _ in
|
||||||
guard let navigationController else {
|
guard let navigationController else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -2645,14 +2645,16 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
var isPremium = false
|
var isPremium = false
|
||||||
var isVerified = false
|
var isVerified = false
|
||||||
var isFake = false
|
var isFake = false
|
||||||
let smallTitleString: NSAttributedString
|
let titleStringText: String
|
||||||
let titleString: NSAttributedString
|
let smallTitleAttributes: MultiScaleTextState.Attributes
|
||||||
let smallSubtitleString: NSAttributedString
|
let titleAttributes: MultiScaleTextState.Attributes
|
||||||
let subtitleString: NSAttributedString
|
let subtitleStringText: String
|
||||||
|
let smallSubtitleAttributes: MultiScaleTextState.Attributes
|
||||||
|
let subtitleAttributes: MultiScaleTextState.Attributes
|
||||||
var subtitleIsButton: Bool = false
|
var subtitleIsButton: Bool = false
|
||||||
var panelSubtitleString: NSAttributedString?
|
var panelSubtitleString: (text: String, attributes: MultiScaleTextState.Attributes)?
|
||||||
var nextPanelSubtitleString: NSAttributedString?
|
var nextPanelSubtitleString: (text: String, attributes: MultiScaleTextState.Attributes)?
|
||||||
let usernameString: NSAttributedString
|
let usernameString: (text: String, attributes: MultiScaleTextState.Attributes)
|
||||||
if let peer = peer {
|
if let peer = peer {
|
||||||
isPremium = peer.isPremium
|
isPremium = peer.isPremium
|
||||||
isVerified = peer.isVerified
|
isVerified = peer.isVerified
|
||||||
@ -2681,17 +2683,21 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
titleString = NSAttributedString(string: title, font: Font.regular(30.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
|
titleStringText = title
|
||||||
smallTitleString = NSAttributedString(string: title, font: Font.regular(30.0), textColor: .white)
|
titleAttributes = MultiScaleTextState.Attributes(font: Font.regular(30.0), color: presentationData.theme.list.itemPrimaryTextColor)
|
||||||
|
smallTitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(30.0), color: .white)
|
||||||
|
|
||||||
if self.isSettings, let user = peer as? TelegramUser {
|
if self.isSettings, let user = peer as? TelegramUser {
|
||||||
var subtitle = formatPhoneNumber(context: self.context, number: user.phone ?? "")
|
var subtitle = formatPhoneNumber(context: self.context, number: user.phone ?? "")
|
||||||
|
|
||||||
if let mainUsername = user.addressName, !mainUsername.isEmpty {
|
if let mainUsername = user.addressName, !mainUsername.isEmpty {
|
||||||
subtitle = "\(subtitle) • @\(mainUsername)"
|
subtitle = "\(subtitle) • @\(mainUsername)"
|
||||||
}
|
}
|
||||||
smallSubtitleString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7))
|
subtitleStringText = subtitle
|
||||||
subtitleString = NSAttributedString(string: subtitle, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(17.0), color: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
|
||||||
|
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor))
|
||||||
} else if let _ = threadData {
|
} else if let _ = threadData {
|
||||||
let subtitleColor: UIColor
|
let subtitleColor: UIColor
|
||||||
subtitleColor = presentationData.theme.list.itemAccentColor
|
subtitleColor = presentationData.theme.list.itemAccentColor
|
||||||
@ -2699,9 +2705,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
let statusText: String
|
let statusText: String
|
||||||
statusText = peer.debugDisplayTitle
|
statusText = peer.debugDisplayTitle
|
||||||
|
|
||||||
smallSubtitleString = NSAttributedString(string: statusText, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7))
|
subtitleStringText = statusText
|
||||||
subtitleString = NSAttributedString(string: statusText, font: Font.semibold(15.0), textColor: subtitleColor)
|
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.semibold(15.0), color: subtitleColor)
|
||||||
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(15.0), color: UIColor(white: 1.0, alpha: 0.7))
|
||||||
|
|
||||||
|
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor))
|
||||||
|
|
||||||
subtitleIsButton = true
|
subtitleIsButton = true
|
||||||
|
|
||||||
@ -2713,10 +2721,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
subtitleColor = presentationData.theme.list.itemSecondaryTextColor
|
subtitleColor = presentationData.theme.list.itemSecondaryTextColor
|
||||||
}
|
}
|
||||||
panelSubtitleString = NSAttributedString(string: panelStatusData.text, font: Font.regular(17.0), textColor: subtitleColor)
|
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
|
||||||
}
|
}
|
||||||
if let nextPanelStatusData = maybeNextPanelStatusData {
|
if let nextPanelStatusData = maybeNextPanelStatusData {
|
||||||
nextPanelSubtitleString = NSAttributedString(string: nextPanelStatusData.text, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
nextPanelSubtitleString = (nextPanelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: presentationData.theme.list.itemSecondaryTextColor))
|
||||||
}
|
}
|
||||||
} else if let statusData = statusData {
|
} else if let statusData = statusData {
|
||||||
let subtitleColor: UIColor
|
let subtitleColor: UIColor
|
||||||
@ -2725,9 +2733,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
subtitleColor = presentationData.theme.list.itemSecondaryTextColor
|
subtitleColor = presentationData.theme.list.itemSecondaryTextColor
|
||||||
}
|
}
|
||||||
smallSubtitleString = NSAttributedString(string: statusData.text, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7))
|
|
||||||
subtitleString = NSAttributedString(string: statusData.text, font: Font.regular(17.0), textColor: subtitleColor)
|
subtitleStringText = statusData.text
|
||||||
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor)
|
||||||
|
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(15.0), color: UIColor(white: 1.0, alpha: 0.7))
|
||||||
|
|
||||||
|
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor))
|
||||||
|
|
||||||
let (maybePanelStatusData, maybeNextPanelStatusData, _) = panelStatusData
|
let (maybePanelStatusData, maybeNextPanelStatusData, _) = panelStatusData
|
||||||
if let panelStatusData = maybePanelStatusData {
|
if let panelStatusData = maybePanelStatusData {
|
||||||
@ -2737,22 +2748,28 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
subtitleColor = presentationData.theme.list.itemSecondaryTextColor
|
subtitleColor = presentationData.theme.list.itemSecondaryTextColor
|
||||||
}
|
}
|
||||||
panelSubtitleString = NSAttributedString(string: panelStatusData.text, font: Font.regular(17.0), textColor: subtitleColor)
|
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
|
||||||
}
|
}
|
||||||
if let nextPanelStatusData = maybeNextPanelStatusData {
|
if let nextPanelStatusData = maybeNextPanelStatusData {
|
||||||
nextPanelSubtitleString = NSAttributedString(string: nextPanelStatusData.text, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
nextPanelSubtitleString = (nextPanelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: presentationData.theme.list.itemSecondaryTextColor))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
subtitleString = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
subtitleStringText = " "
|
||||||
smallSubtitleString = subtitleString
|
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
|
||||||
|
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
titleString = NSAttributedString(string: " ", font: Font.semibold(24.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
|
titleStringText = " "
|
||||||
smallTitleString = titleString
|
titleAttributes = MultiScaleTextState.Attributes(font: Font.regular(24.0), color: presentationData.theme.list.itemPrimaryTextColor)
|
||||||
subtitleString = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
smallTitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(24.0), color: .white)
|
||||||
smallSubtitleString = subtitleString
|
|
||||||
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
subtitleStringText = " "
|
||||||
|
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
|
||||||
|
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(15.0), color: presentationData.theme.list.itemSecondaryTextColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
let textSideInset: CGFloat = 36.0
|
let textSideInset: CGFloat = 36.0
|
||||||
@ -2760,17 +2777,17 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
|
|
||||||
let titleConstrainedSize = CGSize(width: width - textSideInset * 2.0 - (isPremium || isVerified || isFake ? 20.0 : 0.0), height: .greatestFiniteMagnitude)
|
let titleConstrainedSize = CGSize(width: width - textSideInset * 2.0 - (isPremium || isVerified || isFake ? 20.0 : 0.0), height: .greatestFiniteMagnitude)
|
||||||
|
|
||||||
let titleNodeLayout = self.titleNode.updateLayout(states: [
|
let titleNodeLayout = self.titleNode.updateLayout(text: titleStringText, states: [
|
||||||
TitleNodeStateRegular: MultiScaleTextState(attributedText: titleString, constrainedSize: titleConstrainedSize),
|
TitleNodeStateRegular: MultiScaleTextState(attributes: titleAttributes, constrainedSize: titleConstrainedSize),
|
||||||
TitleNodeStateExpanded: MultiScaleTextState(attributedText: smallTitleString, constrainedSize: titleConstrainedSize)
|
TitleNodeStateExpanded: MultiScaleTextState(attributes: smallTitleAttributes, constrainedSize: titleConstrainedSize)
|
||||||
], mainState: TitleNodeStateRegular)
|
], mainState: TitleNodeStateRegular)
|
||||||
self.titleNode.accessibilityLabel = titleString.string
|
self.titleNode.accessibilityLabel = titleStringText
|
||||||
|
|
||||||
let subtitleNodeLayout = self.subtitleNode.updateLayout(states: [
|
let subtitleNodeLayout = self.subtitleNode.updateLayout(text: subtitleStringText, states: [
|
||||||
TitleNodeStateRegular: MultiScaleTextState(attributedText: subtitleString, constrainedSize: titleConstrainedSize),
|
TitleNodeStateRegular: MultiScaleTextState(attributes: subtitleAttributes, constrainedSize: titleConstrainedSize),
|
||||||
TitleNodeStateExpanded: MultiScaleTextState(attributedText: smallSubtitleString, constrainedSize: titleConstrainedSize)
|
TitleNodeStateExpanded: MultiScaleTextState(attributes: smallSubtitleAttributes, constrainedSize: titleConstrainedSize)
|
||||||
], mainState: TitleNodeStateRegular)
|
], mainState: TitleNodeStateRegular)
|
||||||
self.subtitleNode.accessibilityLabel = subtitleString.string
|
self.subtitleNode.accessibilityLabel = subtitleStringText
|
||||||
|
|
||||||
if subtitleIsButton {
|
if subtitleIsButton {
|
||||||
let subtitleBackgroundNode: ASDisplayNode
|
let subtitleBackgroundNode: ASDisplayNode
|
||||||
@ -2863,25 +2880,25 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let panelSubtitleNodeLayout = self.panelSubtitleNode.updateLayout(states: [
|
let panelSubtitleNodeLayout = self.panelSubtitleNode.updateLayout(text: panelSubtitleString?.text ?? subtitleStringText, states: [
|
||||||
TitleNodeStateRegular: MultiScaleTextState(attributedText: panelSubtitleString ?? subtitleString, constrainedSize: titleConstrainedSize),
|
TitleNodeStateRegular: MultiScaleTextState(attributes: panelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize),
|
||||||
TitleNodeStateExpanded: MultiScaleTextState(attributedText: panelSubtitleString ?? subtitleString, constrainedSize: titleConstrainedSize)
|
TitleNodeStateExpanded: MultiScaleTextState(attributes: panelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize)
|
||||||
], mainState: TitleNodeStateRegular)
|
], mainState: TitleNodeStateRegular)
|
||||||
self.panelSubtitleNode.accessibilityLabel = (panelSubtitleString ?? subtitleString).string
|
self.panelSubtitleNode.accessibilityLabel = panelSubtitleString?.text ?? subtitleStringText
|
||||||
|
|
||||||
let nextPanelSubtitleNodeLayout = self.nextPanelSubtitleNode.updateLayout(states: [
|
let nextPanelSubtitleNodeLayout = self.nextPanelSubtitleNode.updateLayout(text: nextPanelSubtitleString?.text ?? subtitleStringText, states: [
|
||||||
TitleNodeStateRegular: MultiScaleTextState(attributedText: nextPanelSubtitleString ?? subtitleString, constrainedSize: titleConstrainedSize),
|
TitleNodeStateRegular: MultiScaleTextState(attributes: nextPanelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize),
|
||||||
TitleNodeStateExpanded: MultiScaleTextState(attributedText: nextPanelSubtitleString ?? subtitleString, constrainedSize: titleConstrainedSize)
|
TitleNodeStateExpanded: MultiScaleTextState(attributes: nextPanelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize)
|
||||||
], mainState: TitleNodeStateRegular)
|
], mainState: TitleNodeStateRegular)
|
||||||
if let _ = nextPanelSubtitleString {
|
if let _ = nextPanelSubtitleString {
|
||||||
self.nextPanelSubtitleNode.isHidden = false
|
self.nextPanelSubtitleNode.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let usernameNodeLayout = self.usernameNode.updateLayout(states: [
|
let usernameNodeLayout = self.usernameNode.updateLayout(text: usernameString.text, states: [
|
||||||
TitleNodeStateRegular: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)),
|
TitleNodeStateRegular: MultiScaleTextState(attributes: usernameString.attributes, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)),
|
||||||
TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
|
TitleNodeStateExpanded: MultiScaleTextState(attributes: usernameString.attributes, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
|
||||||
], mainState: TitleNodeStateRegular)
|
], mainState: TitleNodeStateRegular)
|
||||||
self.usernameNode.accessibilityLabel = usernameString.string
|
self.usernameNode.accessibilityLabel = usernameString.text
|
||||||
|
|
||||||
let avatarCenter: CGPoint
|
let avatarCenter: CGPoint
|
||||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||||
@ -2987,7 +3004,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
subtitleAlpha = 1.0 - titleCollapseFraction
|
subtitleAlpha = 1.0 - titleCollapseFraction
|
||||||
panelSubtitleAlpha = 0.0
|
panelSubtitleAlpha = 0.0
|
||||||
} else {
|
} else {
|
||||||
if (panelSubtitleString ?? subtitleString).string != subtitleString.string {
|
if (panelSubtitleString?.text ?? subtitleStringText) != subtitleStringText {
|
||||||
subtitleAlpha = 1.0 - effectiveAreaExpansionFraction
|
subtitleAlpha = 1.0 - effectiveAreaExpansionFraction
|
||||||
panelSubtitleAlpha = effectiveAreaExpansionFraction
|
panelSubtitleAlpha = effectiveAreaExpansionFraction
|
||||||
subtitleOffset = -effectiveAreaExpansionFraction * 5.0
|
subtitleOffset = -effectiveAreaExpansionFraction * 5.0
|
||||||
|
|||||||
@ -7659,7 +7659,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
|
|
||||||
func forwardMessages(messageIds: Set<MessageId>?) {
|
func forwardMessages(messageIds: Set<MessageId>?) {
|
||||||
if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty {
|
if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty {
|
||||||
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true))
|
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true, selectForumThreads: true))
|
||||||
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in
|
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in
|
||||||
guard let strongSelf = self, let strongController = peerSelectionController else {
|
guard let strongSelf = self, let strongController = peerSelectionController else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
public var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)?
|
public var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)?
|
||||||
private let filter: ChatListNodePeersFilter
|
private let filter: ChatListNodePeersFilter
|
||||||
private let forumPeerId: EnginePeer.Id?
|
private let forumPeerId: EnginePeer.Id?
|
||||||
|
private let selectForumThreads: Bool
|
||||||
|
|
||||||
private let attemptSelection: ((Peer, Int64?) -> Void)?
|
private let attemptSelection: ((Peer, Int64?) -> Void)?
|
||||||
private let createNewGroup: (() -> Void)?
|
private let createNewGroup: (() -> Void)?
|
||||||
@ -91,6 +92,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
self.pretendPresentedInModal = params.pretendPresentedInModal
|
self.pretendPresentedInModal = params.pretendPresentedInModal
|
||||||
self.forwardedMessageIds = params.forwardedMessageIds
|
self.forwardedMessageIds = params.forwardedMessageIds
|
||||||
self.hasTypeHeaders = params.hasTypeHeaders
|
self.hasTypeHeaders = params.hasTypeHeaders
|
||||||
|
self.selectForumThreads = params.selectForumThreads
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||||
|
|
||||||
@ -181,7 +183,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
|
|
||||||
self.peerSelectionNode.requestOpenPeer = { [weak self] peer, threadId in
|
self.peerSelectionNode.requestOpenPeer = { [weak self] peer, threadId in
|
||||||
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||||
if let peer = peer as? TelegramChannel, peer.flags.contains(.isForum), threadId == nil {
|
if let peer = peer as? TelegramChannel, peer.flags.contains(.isForum), threadId == nil, strongSelf.selectForumThreads {
|
||||||
let controller = PeerSelectionControllerImpl(
|
let controller = PeerSelectionControllerImpl(
|
||||||
PeerSelectionControllerParams(
|
PeerSelectionControllerParams(
|
||||||
context: strongSelf.context,
|
context: strongSelf.context,
|
||||||
@ -197,7 +199,9 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
pretendPresentedInModal: false,
|
pretendPresentedInModal: false,
|
||||||
multipleSelection: false,
|
multipleSelection: false,
|
||||||
forwardedMessageIds: [],
|
forwardedMessageIds: [],
|
||||||
hasTypeHeaders: false)
|
hasTypeHeaders: false,
|
||||||
|
selectForumThreads: false
|
||||||
|
)
|
||||||
)
|
)
|
||||||
controller.peerSelected = strongSelf.peerSelected
|
controller.peerSelected = strongSelf.peerSelected
|
||||||
strongSelf.push(controller)
|
strongSelf.push(controller)
|
||||||
|
|||||||
@ -663,7 +663,7 @@ public class ShareRootControllerImpl {
|
|||||||
attemptSelectionImpl?(peer)
|
attemptSelectionImpl?(peer)
|
||||||
}, createNewGroup: {
|
}, createNewGroup: {
|
||||||
createNewGroupImpl?()
|
createNewGroupImpl?()
|
||||||
}, pretendPresentedInModal: true))
|
}, pretendPresentedInModal: true, selectForumThreads: true))
|
||||||
|
|
||||||
controller.customDismiss = {
|
controller.customDismiss = {
|
||||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||||
@ -837,7 +837,7 @@ public class ShareRootControllerImpl {
|
|||||||
var attemptSelectionImpl: ((Peer) -> Void)?
|
var attemptSelectionImpl: ((Peer) -> Void)?
|
||||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages, .excludeSecretChats], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: presentationData.strings.ChatImport_Title, attemptSelection: { peer, _ in
|
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages, .excludeSecretChats], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: presentationData.strings.ChatImport_Title, attemptSelection: { peer, _ in
|
||||||
attemptSelectionImpl?(peer)
|
attemptSelectionImpl?(peer)
|
||||||
}, pretendPresentedInModal: true))
|
}, pretendPresentedInModal: true, selectForumThreads: true))
|
||||||
|
|
||||||
controller.customDismiss = {
|
controller.customDismiss = {
|
||||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||||
@ -912,7 +912,7 @@ public class ShareRootControllerImpl {
|
|||||||
attemptSelectionImpl?(peer)
|
attemptSelectionImpl?(peer)
|
||||||
}, createNewGroup: {
|
}, createNewGroup: {
|
||||||
createNewGroupImpl?()
|
createNewGroupImpl?()
|
||||||
}, pretendPresentedInModal: true))
|
}, pretendPresentedInModal: true, selectForumThreads: true))
|
||||||
|
|
||||||
controller.customDismiss = {
|
controller.customDismiss = {
|
||||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user