mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
WIP
This commit is contained in:
parent
7a1b0d3364
commit
5ad4331eeb
25 changed files with 1776 additions and 285 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -32,3 +32,6 @@ url=../tgcalls.git
|
||||||
[submodule "third-party/td/td"]
|
[submodule "third-party/td/td"]
|
||||||
path = third-party/td/td
|
path = third-party/td/td
|
||||||
url = https://github.com/tdlib/td
|
url = https://github.com/tdlib/td
|
||||||
|
[submodule "third-party/XcodeGen"]
|
||||||
|
path = third-party/XcodeGen
|
||||||
|
url = https://github.com/yonaskolb/XcodeGen.git
|
||||||
|
|
|
||||||
|
|
@ -1137,6 +1137,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||||
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, stories: Bool, forceDark: Bool) -> ViewController
|
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, stories: Bool, forceDark: Bool) -> ViewController
|
||||||
func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController
|
func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController
|
||||||
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
||||||
|
func makeStorySelectionController(context: AccountContext, peerId: EnginePeer.Id, completion: @escaping ([EngineStoryItem]) -> Void) -> ViewController
|
||||||
func makeArchiveSettingsController(context: AccountContext) -> ViewController
|
func makeArchiveSettingsController(context: AccountContext) -> ViewController
|
||||||
func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController
|
func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||||
func makeBusinessSetupScreen(context: AccountContext) -> ViewController
|
func makeBusinessSetupScreen(context: AccountContext) -> ViewController
|
||||||
|
|
|
||||||
|
|
@ -165,8 +165,10 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let text: String
|
private let text: String
|
||||||
private let titleFont: PromptControllerTitleFont
|
private let titleFont: PromptControllerTitleFont
|
||||||
|
private let subtitle: String?
|
||||||
|
|
||||||
private let textNode: ASTextNode
|
private let textNode: ASTextNode
|
||||||
|
private let subtitleNode: ASTextNode?
|
||||||
let inputFieldNode: PromptInputFieldNode
|
let inputFieldNode: PromptInputFieldNode
|
||||||
|
|
||||||
private let actionNodesSeparator: ASDisplayNode
|
private let actionNodesSeparator: ASDisplayNode
|
||||||
|
|
@ -189,14 +191,23 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||||
return self.isUserInteractionEnabled
|
return self.isUserInteractionEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?, placeholder: String?, characterLimit: Int) {
|
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, subtitle: String?, value: String?, placeholder: String?, characterLimit: Int) {
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.text = text
|
self.text = text
|
||||||
self.titleFont = titleFont
|
self.titleFont = titleFont
|
||||||
|
self.subtitle = subtitle
|
||||||
|
|
||||||
self.textNode = ASTextNode()
|
self.textNode = ASTextNode()
|
||||||
self.textNode.maximumNumberOfLines = 2
|
self.textNode.maximumNumberOfLines = 2
|
||||||
|
|
||||||
|
if subtitle != nil {
|
||||||
|
let subtitleNode = ASTextNode()
|
||||||
|
subtitleNode.maximumNumberOfLines = 0
|
||||||
|
self.subtitleNode = subtitleNode
|
||||||
|
} else {
|
||||||
|
self.subtitleNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: placeholder ?? "", characterLimit: characterLimit)
|
self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: placeholder ?? "", characterLimit: characterLimit)
|
||||||
self.inputFieldNode.text = value ?? ""
|
self.inputFieldNode.text = value ?? ""
|
||||||
|
|
||||||
|
|
@ -220,6 +231,9 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
|
if let subtitleNode = self.subtitleNode {
|
||||||
|
self.addSubnode(subtitleNode)
|
||||||
|
}
|
||||||
|
|
||||||
self.addSubnode(self.inputFieldNode)
|
self.addSubnode(self.inputFieldNode)
|
||||||
|
|
||||||
|
|
@ -268,6 +282,10 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||||
titleFontValue = Font.semibold(17.0)
|
titleFontValue = Font.semibold(17.0)
|
||||||
}
|
}
|
||||||
self.textNode.attributedText = NSAttributedString(string: self.text, font: titleFontValue, textColor: theme.primaryColor, paragraphAlignment: .center)
|
self.textNode.attributedText = NSAttributedString(string: self.text, font: titleFontValue, textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||||
|
|
||||||
|
if let subtitle = self.subtitle, let subtitleNode = self.subtitleNode {
|
||||||
|
subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||||
|
}
|
||||||
|
|
||||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||||
for actionNode in self.actionNodes {
|
for actionNode in self.actionNodes {
|
||||||
|
|
@ -302,6 +320,14 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||||
origin.y += textSize.height + 6.0 + spacing
|
origin.y += textSize.height + 6.0 + spacing
|
||||||
|
|
||||||
|
var subtitleSize: CGSize?
|
||||||
|
if let subtitleNode {
|
||||||
|
let subtitleSizeValue = subtitleNode.measure(measureSize)
|
||||||
|
subtitleSize = subtitleSizeValue
|
||||||
|
transition.updateFrame(node: subtitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSizeValue.width) / 2.0), y: origin.y), size: subtitleSizeValue))
|
||||||
|
origin.y += subtitleSizeValue.height + 6.0 + spacing
|
||||||
|
}
|
||||||
|
|
||||||
let actionButtonHeight: CGFloat = 44.0
|
let actionButtonHeight: CGFloat = 44.0
|
||||||
var minActionsWidth: CGFloat = 0.0
|
var minActionsWidth: CGFloat = 0.0
|
||||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||||
|
|
@ -324,6 +350,9 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0)
|
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0)
|
||||||
|
|
||||||
var contentWidth = max(titleSize.width, minActionsWidth)
|
var contentWidth = max(titleSize.width, minActionsWidth)
|
||||||
|
if let subtitleSize {
|
||||||
|
contentWidth = max(contentWidth, subtitleSize.width)
|
||||||
|
}
|
||||||
contentWidth = max(contentWidth, 234.0)
|
contentWidth = max(contentWidth, 234.0)
|
||||||
|
|
||||||
var actionsHeight: CGFloat = 0.0
|
var actionsHeight: CGFloat = 0.0
|
||||||
|
|
@ -342,7 +371,10 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||||
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
|
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
|
||||||
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
|
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
|
||||||
|
|
||||||
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom)
|
var resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom)
|
||||||
|
if let subtitleSize {
|
||||||
|
resultSize.height += subtitleSize.height + spacing
|
||||||
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
|
@ -407,7 +439,7 @@ public enum PromptControllerTitleFont {
|
||||||
case bold
|
case bold
|
||||||
}
|
}
|
||||||
|
|
||||||
public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, placeholder: String? = nil, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController {
|
public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, subtitle: String? = nil, value: String?, placeholder: String? = nil, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController {
|
||||||
let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
|
let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var dismissImpl: ((Bool) -> Void)?
|
var dismissImpl: ((Bool) -> Void)?
|
||||||
|
|
@ -421,7 +453,7 @@ public func promptController(sharedContext: SharedAccountContext, updatedPresent
|
||||||
applyImpl?()
|
applyImpl?()
|
||||||
})]
|
})]
|
||||||
|
|
||||||
let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value, placeholder: placeholder, characterLimit: characterLimit)
|
let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, subtitle: subtitle, value: value, placeholder: placeholder, characterLimit: characterLimit)
|
||||||
contentNode.complete = {
|
contentNode.complete = {
|
||||||
dismissImpl?(true)
|
dismissImpl?(true)
|
||||||
applyImpl?()
|
applyImpl?()
|
||||||
|
|
|
||||||
|
|
@ -2127,7 +2127,7 @@ public func channelStatsController(
|
||||||
}
|
}
|
||||||
messagesPromise.set(.single(nil) |> then(messageView))
|
messagesPromise.set(.single(nil) |> then(messageView))
|
||||||
|
|
||||||
let storyList = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
|
let storyList = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
|
||||||
storyList.loadMore()
|
storyList.loadMore()
|
||||||
storiesPromise.set(
|
storiesPromise.set(
|
||||||
.single(nil)
|
.single(nil)
|
||||||
|
|
|
||||||
|
|
@ -943,7 +943,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||||
dict[2109703795] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
dict[2109703795] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
||||||
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
||||||
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
|
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
|
||||||
dict[2139438098] = { return Api.StarGift.parse_starGift($0) }
|
dict[12386139] = { return Api.StarGift.parse_starGift($0) }
|
||||||
dict[-164136786] = { return Api.StarGift.parse_starGiftUnique($0) }
|
dict[-164136786] = { return Api.StarGift.parse_starGiftUnique($0) }
|
||||||
dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
|
dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
|
||||||
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
|
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
|
||||||
|
|
|
||||||
|
|
@ -636,14 +636,14 @@ public extension Api {
|
||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum StarGift: TypeConstructorDescription {
|
enum StarGift: TypeConstructorDescription {
|
||||||
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?)
|
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?)
|
||||||
case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?, releasedBy: Api.Peer?)
|
case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?, releasedBy: Api.Peer?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy):
|
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(2139438098)
|
buffer.appendInt32(12386139)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt64(id, buffer: buffer, boxed: false)
|
serializeInt64(id, buffer: buffer, boxed: false)
|
||||||
|
|
@ -659,6 +659,8 @@ public extension Api {
|
||||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellMinStars!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellMinStars!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 5) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 5) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 6) != 0 {releasedBy!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 6) != 0 {releasedBy!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserTotal!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserRemains!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy):
|
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy):
|
||||||
if boxed {
|
if boxed {
|
||||||
|
|
@ -688,8 +690,8 @@ public extension Api {
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy):
|
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains):
|
||||||
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any)])
|
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any)])
|
||||||
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy):
|
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy):
|
||||||
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any), ("releasedBy", releasedBy as Any)])
|
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any), ("releasedBy", releasedBy as Any)])
|
||||||
}
|
}
|
||||||
|
|
@ -728,6 +730,10 @@ public extension Api {
|
||||||
if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
|
if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
|
||||||
_14 = Api.parse(reader, signature: signature) as? Api.Peer
|
_14 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||||
} }
|
} }
|
||||||
|
var _15: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 8) != 0 {_15 = reader.readInt32() }
|
||||||
|
var _16: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 8) != 0 {_16 = reader.readInt32() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
|
|
@ -742,8 +748,10 @@ public extension Api {
|
||||||
let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil
|
let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil
|
||||||
let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil
|
let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil
|
||||||
let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil
|
let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
|
let _c15 = (Int(_1!) & Int(1 << 8) == 0) || _15 != nil
|
||||||
return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14)
|
let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 {
|
||||||
|
return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14, perUserTotal: _15, perUserRemains: _16)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||||
|
|
||||||
public class Serialization: NSObject, MTSerialization {
|
public class Serialization: NSObject, MTSerialization {
|
||||||
public func currentLayer() -> UInt {
|
public func currentLayer() -> UInt {
|
||||||
return 207
|
return 210
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parseMessage(_ data: Data!) -> Any! {
|
public func parseMessage(_ data: Data!) -> Any! {
|
||||||
|
|
|
||||||
|
|
@ -531,11 +531,13 @@ private final class CachedPeerStoryListHead: Codable {
|
||||||
let items: [Stories.StoredItem]
|
let items: [Stories.StoredItem]
|
||||||
let pinnedIds: [Int32]
|
let pinnedIds: [Int32]
|
||||||
let totalCount: Int32
|
let totalCount: Int32
|
||||||
|
let folders: [StoryListContext.State.Folder]
|
||||||
|
|
||||||
init(items: [Stories.StoredItem], pinnedIds: [Int32], totalCount: Int32) {
|
init(items: [Stories.StoredItem], pinnedIds: [Int32], totalCount: Int32, folders: [StoryListContext.State.Folder]) {
|
||||||
self.items = items
|
self.items = items
|
||||||
self.pinnedIds = pinnedIds
|
self.pinnedIds = pinnedIds
|
||||||
self.totalCount = totalCount
|
self.totalCount = totalCount
|
||||||
|
self.folders = folders
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -578,9 +580,20 @@ public struct StoryListContextState: Equatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Folder: Equatable, Codable {
|
||||||
|
public let id: Int64
|
||||||
|
public let title: String
|
||||||
|
|
||||||
|
public init(id: Int64, title: String) {
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var peerReference: PeerReference?
|
public var peerReference: PeerReference?
|
||||||
public var items: [Item]
|
public var items: [Item]
|
||||||
public var availableLanguages: [Language]
|
public var availableLanguages: [Language]
|
||||||
|
public var availableFolders: [Folder]
|
||||||
public var pinnedIds: [Int32]
|
public var pinnedIds: [Int32]
|
||||||
public var totalCount: Int
|
public var totalCount: Int
|
||||||
public var loadMoreToken: AnyHashable?
|
public var loadMoreToken: AnyHashable?
|
||||||
|
|
@ -592,6 +605,7 @@ public struct StoryListContextState: Equatable {
|
||||||
peerReference: PeerReference?,
|
peerReference: PeerReference?,
|
||||||
items: [Item],
|
items: [Item],
|
||||||
availableLanguages: [Language],
|
availableLanguages: [Language],
|
||||||
|
availableFolders: [Folder],
|
||||||
pinnedIds: [Int32],
|
pinnedIds: [Int32],
|
||||||
totalCount: Int,
|
totalCount: Int,
|
||||||
loadMoreToken: AnyHashable?,
|
loadMoreToken: AnyHashable?,
|
||||||
|
|
@ -603,6 +617,7 @@ public struct StoryListContextState: Equatable {
|
||||||
self.peerReference = peerReference
|
self.peerReference = peerReference
|
||||||
self.items = items
|
self.items = items
|
||||||
self.availableLanguages = availableLanguages
|
self.availableLanguages = availableLanguages
|
||||||
|
self.availableFolders = availableFolders
|
||||||
self.pinnedIds = pinnedIds
|
self.pinnedIds = pinnedIds
|
||||||
self.totalCount = totalCount
|
self.totalCount = totalCount
|
||||||
self.loadMoreToken = loadMoreToken
|
self.loadMoreToken = loadMoreToken
|
||||||
|
|
@ -621,12 +636,13 @@ public protocol StoryListContext: AnyObject {
|
||||||
func loadMore(completion: (() -> Void)?)
|
func loadMore(completion: (() -> Void)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class PeerStoryListContext: StoryListContext {
|
public final class PeerStoryListContext: StoryListContext {
|
||||||
private final class Impl {
|
private final class Impl {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let peerId: EnginePeer.Id
|
private let peerId: EnginePeer.Id
|
||||||
private let isArchived: Bool
|
private let isArchived: Bool
|
||||||
|
private let folderId: Int64?
|
||||||
|
|
||||||
private let statePromise = Promise<State>()
|
private let statePromise = Promise<State>()
|
||||||
private var stateValue: State {
|
private var stateValue: State {
|
||||||
|
|
@ -645,22 +661,40 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
|
|
||||||
private var completionCallbacksByToken: [AnyHashable: [() -> Void]] = [:]
|
private var completionCallbacksByToken: [AnyHashable: [() -> Void]] = [:]
|
||||||
|
|
||||||
init(queue: Queue, account: Account, peerId: EnginePeer.Id, isArchived: Bool) {
|
init(queue: Queue, account: Account, peerId: EnginePeer.Id, isArchived: Bool, folderId: Int64?) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.account = account
|
self.account = account
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.isArchived = isArchived
|
self.isArchived = isArchived
|
||||||
|
self.folderId = folderId
|
||||||
|
|
||||||
self.stateValue = State(peerReference: nil, items: [], availableLanguages: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: false, allEntityFiles: [:], isLoading: false)
|
self.stateValue = State(peerReference: nil, items: [], availableLanguages: [], availableFolders: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: false, allEntityFiles: [:], isLoading: false)
|
||||||
|
|
||||||
let _ = (account.postbox.transaction { transaction -> (PeerReference?, [State.Item], [Int32], Int, [MediaId: TelegramMediaFile], Bool) in
|
let _ = (account.postbox.transaction { transaction -> (PeerReference?, [State.Item], [Int32], Int, [MediaId: TelegramMediaFile], [StoryListContext.State.Folder], Bool) in
|
||||||
let key = ValueBoxKey(length: 8 + 1)
|
let key = ValueBoxKey(length: 8 + 1)
|
||||||
key.setInt64(0, value: peerId.toInt64())
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
key.setInt8(8, value: isArchived ? 1 : 0)
|
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||||
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
|
let cachedMain = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
|
||||||
guard let cached = cached else {
|
guard let cachedMain else {
|
||||||
return (nil, [], [], 0, [:], false)
|
return (nil, [], [], 0, [:], [], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cached: CachedPeerStoryListHead
|
||||||
|
if let folderId {
|
||||||
|
let key = ValueBoxKey(length: 8 + 1 + 8)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||||
|
key.setInt64(8 + 1, value: folderId)
|
||||||
|
let cachedFolder = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
|
||||||
|
if let cachedFolder {
|
||||||
|
cached = cachedFolder
|
||||||
|
} else {
|
||||||
|
return (nil, [], [], 0, [:], cachedMain.folders, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cached = cachedMain
|
||||||
|
}
|
||||||
|
|
||||||
var items: [State.Item] = []
|
var items: [State.Item] = []
|
||||||
var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||||
for storedItem in cached.items {
|
for storedItem in cached.items {
|
||||||
|
|
@ -734,28 +768,30 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
|
|
||||||
let peerReference = transaction.getPeer(peerId).flatMap(PeerReference.init)
|
let peerReference = transaction.getPeer(peerId).flatMap(PeerReference.init)
|
||||||
|
|
||||||
return (peerReference, items, cached.pinnedIds, Int(cached.totalCount), allEntityFiles, true)
|
return (peerReference, items, cached.pinnedIds, Int(cached.totalCount), allEntityFiles, cached.folders, true)
|
||||||
}
|
}
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] peerReference, items, pinnedIds, totalCount, allEntityFiles, hasCache in
|
|> deliverOn(self.queue)).start(next: { [weak self] peerReference, items, pinnedIds, totalCount, allEntityFiles, folders, hasCache in
|
||||||
guard let `self` = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedState = State(peerReference: peerReference, items: items, availableLanguages: [], pinnedIds: pinnedIds, totalCount: totalCount, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: hasCache, allEntityFiles: allEntityFiles, isLoading: false)
|
var updatedState = State(peerReference: peerReference, items: items, availableLanguages: [], availableFolders: folders, pinnedIds: pinnedIds, totalCount: totalCount, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: hasCache, allEntityFiles: allEntityFiles, isLoading: false)
|
||||||
updatedState.items.sort(by: { lhs, rhs in
|
if self.folderId == nil {
|
||||||
let lhsPinned = updatedState.pinnedIds.firstIndex(of: lhs.storyItem.id)
|
updatedState.items.sort(by: { lhs, rhs in
|
||||||
let rhsPinned = updatedState.pinnedIds.firstIndex(of: rhs.storyItem.id)
|
let lhsPinned = updatedState.pinnedIds.firstIndex(of: lhs.storyItem.id)
|
||||||
|
let rhsPinned = updatedState.pinnedIds.firstIndex(of: rhs.storyItem.id)
|
||||||
if let lhsPinned, let rhsPinned {
|
|
||||||
if lhsPinned != rhsPinned {
|
if let lhsPinned, let rhsPinned {
|
||||||
return lhsPinned < rhsPinned
|
if lhsPinned != rhsPinned {
|
||||||
|
return lhsPinned < rhsPinned
|
||||||
|
}
|
||||||
|
} else if (lhsPinned == nil) != (rhsPinned == nil) {
|
||||||
|
return lhsPinned != nil
|
||||||
}
|
}
|
||||||
} else if (lhsPinned == nil) != (rhsPinned == nil) {
|
|
||||||
return lhsPinned != nil
|
return lhs.storyItem.timestamp > rhs.storyItem.timestamp
|
||||||
}
|
})
|
||||||
|
}
|
||||||
return lhs.storyItem.timestamp > rhs.storyItem.timestamp
|
|
||||||
})
|
|
||||||
self.stateValue = updatedState
|
self.stateValue = updatedState
|
||||||
|
|
||||||
self.loadMore(completion: nil)
|
self.loadMore(completion: nil)
|
||||||
|
|
@ -791,6 +827,8 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
let account = self.account
|
let account = self.account
|
||||||
let accountPeerId = account.peerId
|
let accountPeerId = account.peerId
|
||||||
let isArchived = self.isArchived
|
let isArchived = self.isArchived
|
||||||
|
let folderId = self.folderId
|
||||||
|
let folders = self.stateValue.availableFolders
|
||||||
self.requestDisposable = (self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
self.requestDisposable = (self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
}
|
}
|
||||||
|
|
@ -822,6 +860,73 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
var totalCount: Int = 0
|
var totalCount: Int = 0
|
||||||
var hasMore: Bool = false
|
var hasMore: Bool = false
|
||||||
|
|
||||||
|
if let folderId {
|
||||||
|
let key = ValueBoxKey(length: 8 + 1 + 8)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||||
|
key.setInt64(8 + 1, value: folderId)
|
||||||
|
|
||||||
|
var updatedItems: [Stories.StoredItem] = []
|
||||||
|
if let currentEntry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
|
||||||
|
updatedItems = currentEntry.items
|
||||||
|
}
|
||||||
|
|
||||||
|
if let entry = CodableEntry(CachedPeerStoryListHead(items: updatedItems, pinnedIds: [], totalCount: Int32(updatedItems.count), folders: [])) {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in updatedItems {
|
||||||
|
if case let .item(item) = item, let media = item.media {
|
||||||
|
let mappedItem = EngineStoryItem(
|
||||||
|
id: item.id,
|
||||||
|
timestamp: item.timestamp,
|
||||||
|
expirationTimestamp: item.expirationTimestamp,
|
||||||
|
media: EngineMedia(media),
|
||||||
|
alternativeMediaList: item.alternativeMediaList.map(EngineMedia.init),
|
||||||
|
mediaAreas: item.mediaAreas,
|
||||||
|
text: item.text,
|
||||||
|
entities: item.entities,
|
||||||
|
views: item.views.flatMap { views in
|
||||||
|
return EngineStoryItem.Views(
|
||||||
|
seenCount: views.seenCount,
|
||||||
|
reactedCount: views.reactedCount,
|
||||||
|
forwardCount: views.forwardCount,
|
||||||
|
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
|
||||||
|
return transaction.getPeer(id).flatMap(EnginePeer.init)
|
||||||
|
},
|
||||||
|
reactions: views.reactions,
|
||||||
|
hasList: views.hasList
|
||||||
|
)
|
||||||
|
},
|
||||||
|
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
|
||||||
|
isPinned: item.isPinned,
|
||||||
|
isExpired: item.isExpired,
|
||||||
|
isPublic: item.isPublic,
|
||||||
|
isPending: false,
|
||||||
|
isCloseFriends: item.isCloseFriends,
|
||||||
|
isContacts: item.isContacts,
|
||||||
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
|
myReaction: item.myReaction,
|
||||||
|
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||||
|
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
|
||||||
|
)
|
||||||
|
storyItems.append(State.Item(
|
||||||
|
id: StoryId(peerId: peerId, id: mappedItem.id),
|
||||||
|
storyItem: mappedItem,
|
||||||
|
peer: nil
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCount = storyItems.count
|
||||||
|
hasMore = false
|
||||||
|
|
||||||
|
return (storyItems, totalCount, transaction.getPeer(peerId).flatMap(PeerReference.init), hasMore)
|
||||||
|
}
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case let .stories(_, count, stories, pinnedStories, chats, users):
|
case let .stories(_, count, stories, pinnedStories, chats, users):
|
||||||
totalCount = Int(count)
|
totalCount = Int(count)
|
||||||
|
|
@ -883,7 +988,7 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
let key = ValueBoxKey(length: 8 + 1)
|
let key = ValueBoxKey(length: 8 + 1)
|
||||||
key.setInt64(0, value: peerId.toInt64())
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
key.setInt8(8, value: isArchived ? 1 : 0)
|
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||||
if let entry = CodableEntry(CachedPeerStoryListHead(items: storyItems.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: count)) {
|
if let entry = CodableEntry(CachedPeerStoryListHead(items: storyItems.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: count, folders: folders)) {
|
||||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -939,7 +1044,7 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.updatesDisposable == nil {
|
if self.updatesDisposable == nil && self.folderId == nil {
|
||||||
self.updatesDisposable = (self.account.stateManager.storyUpdates
|
self.updatesDisposable = (self.account.stateManager.storyUpdates
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] updates in
|
|> deliverOn(self.queue)).start(next: { [weak self] updates in
|
||||||
guard let `self` = self else {
|
guard let `self` = self else {
|
||||||
|
|
@ -1255,11 +1360,12 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
let items = finalUpdatedState.items
|
let items = finalUpdatedState.items
|
||||||
let pinnedIds = finalUpdatedState.pinnedIds
|
let pinnedIds = finalUpdatedState.pinnedIds
|
||||||
let totalCount = finalUpdatedState.totalCount
|
let totalCount = finalUpdatedState.totalCount
|
||||||
|
let folders = finalUpdatedState.availableFolders
|
||||||
let _ = (self.account.postbox.transaction { transaction -> Void in
|
let _ = (self.account.postbox.transaction { transaction -> Void in
|
||||||
let key = ValueBoxKey(length: 8 + 1)
|
let key = ValueBoxKey(length: 8 + 1)
|
||||||
key.setInt64(0, value: peerId.toInt64())
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
key.setInt8(8, value: isArchived ? 1 : 0)
|
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||||
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount))) {
|
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount), folders: folders)) {
|
||||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
||||||
}
|
}
|
||||||
}).start()
|
}).start()
|
||||||
|
|
@ -1269,6 +1375,242 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addFolder(title: String) -> Int64 {
|
||||||
|
let id = Int64.random(in: Int64.min ... Int64.max)
|
||||||
|
|
||||||
|
var state = self.stateValue
|
||||||
|
state.availableFolders.append(StoryListContextState.Folder(id: id, title: title))
|
||||||
|
self.stateValue = state
|
||||||
|
|
||||||
|
let peerId = self.peerId
|
||||||
|
let isArchived = self.isArchived
|
||||||
|
let items = state.items
|
||||||
|
let pinnedIds = state.pinnedIds
|
||||||
|
let totalCount = state.totalCount
|
||||||
|
let folders = state.availableFolders
|
||||||
|
let _ = (self.account.postbox.transaction { transaction -> Void in
|
||||||
|
let key = ValueBoxKey(length: 8 + 1)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||||
|
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount), folders: folders)) {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFolder(id: Int64) {
|
||||||
|
var state = self.stateValue
|
||||||
|
if let index = state.availableFolders.firstIndex(where: { $0.id == id }) {
|
||||||
|
state.availableFolders.remove(at: index)
|
||||||
|
}
|
||||||
|
self.stateValue = state
|
||||||
|
|
||||||
|
let peerId = self.peerId
|
||||||
|
let isArchived = self.isArchived
|
||||||
|
let items = state.items
|
||||||
|
let pinnedIds = state.pinnedIds
|
||||||
|
let totalCount = state.totalCount
|
||||||
|
let folders = state.availableFolders
|
||||||
|
let _ = (self.account.postbox.transaction { transaction -> Void in
|
||||||
|
let key = ValueBoxKey(length: 8 + 1)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||||
|
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount), folders: folders)) {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToFolder(id: Int64, items: [EngineStoryItem]) {
|
||||||
|
let peerId = self.peerId
|
||||||
|
let _ = (self.account.postbox.transaction { transaction -> Void in
|
||||||
|
let key = ValueBoxKey(length: 8 + 1 + 8)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: 0)
|
||||||
|
key.setInt64(8 + 1, value: id)
|
||||||
|
|
||||||
|
var updatedItems: [Stories.Item] = []
|
||||||
|
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
|
||||||
|
updatedItems = cached.items.compactMap { item -> Stories.Item? in
|
||||||
|
switch item {
|
||||||
|
case let .item(item):
|
||||||
|
return item
|
||||||
|
case .placeholder:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for item in items {
|
||||||
|
let mappedItem = Stories.Item(
|
||||||
|
id: item.id,
|
||||||
|
timestamp: item.timestamp,
|
||||||
|
expirationTimestamp: item.expirationTimestamp,
|
||||||
|
media: item.media._asMedia(),
|
||||||
|
alternativeMediaList: item.alternativeMediaList.map({ $0._asMedia() }),
|
||||||
|
mediaAreas: item.mediaAreas,
|
||||||
|
text: item.text,
|
||||||
|
entities: item.entities,
|
||||||
|
views: item.views.flatMap {
|
||||||
|
return Stories.Item.Views(
|
||||||
|
seenCount: $0.seenCount,
|
||||||
|
reactedCount: $0.reactedCount,
|
||||||
|
forwardCount: $0.forwardCount,
|
||||||
|
seenPeerIds: $0.seenPeers.map { $0.id },
|
||||||
|
reactions: $0.reactions,
|
||||||
|
hasList: $0.hasList
|
||||||
|
)
|
||||||
|
},
|
||||||
|
privacy: item.privacy.flatMap {
|
||||||
|
return Stories.Item.Privacy(
|
||||||
|
base: $0.base,
|
||||||
|
additionallyIncludePeers: $0.additionallyIncludePeers
|
||||||
|
)
|
||||||
|
},
|
||||||
|
isPinned: item.isPinned,
|
||||||
|
isExpired: item.isExpired,
|
||||||
|
isPublic: item.isPublic,
|
||||||
|
isCloseFriends: item.isCloseFriends,
|
||||||
|
isContacts: item.isContacts,
|
||||||
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
|
myReaction: item.myReaction,
|
||||||
|
forwardInfo: item.forwardInfo.flatMap {
|
||||||
|
switch $0 {
|
||||||
|
case let .known(peer, storyId, isModified):
|
||||||
|
return .known(peerId: peer.id, storyId: storyId, isModified: isModified)
|
||||||
|
case let .unknown(name, isModified):
|
||||||
|
return .unknown(name: name, isModified: isModified)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authorId: item.author?.id
|
||||||
|
)
|
||||||
|
if !updatedItems.contains(where: { $0.id == mappedItem.id }) {
|
||||||
|
updatedItems.insert(mappedItem, at: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let entry = CodableEntry(CachedPeerStoryListHead(items: updatedItems.map { .item($0) }, pinnedIds: [], totalCount: Int32(updatedItems.count), folders: [])) {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
|
||||||
|
if self.folderId == id {
|
||||||
|
var state = self.stateValue
|
||||||
|
for item in items {
|
||||||
|
state.items.removeAll(where: { $0.id.id == item.id })
|
||||||
|
state.items.insert(StoryListContextState.Item(
|
||||||
|
id: StoryId(peerId: self.peerId, id: item.id),
|
||||||
|
storyItem: item,
|
||||||
|
peer: nil
|
||||||
|
), at: 0)
|
||||||
|
}
|
||||||
|
self.stateValue = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromFolder(id: Int64, itemIds: [Int32]) {
|
||||||
|
let peerId = self.peerId
|
||||||
|
let _ = (self.account.postbox.transaction { transaction -> Void in
|
||||||
|
let key = ValueBoxKey(length: 8 + 1 + 8)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: 0)
|
||||||
|
key.setInt64(8 + 1, value: id)
|
||||||
|
|
||||||
|
var updatedItems: [Stories.Item] = []
|
||||||
|
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
|
||||||
|
updatedItems = cached.items.compactMap { item -> Stories.Item? in
|
||||||
|
switch item {
|
||||||
|
case let .item(item):
|
||||||
|
return item
|
||||||
|
case .placeholder:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for itemId in itemIds {
|
||||||
|
if let index = updatedItems.firstIndex(where: { $0.id == itemId }) {
|
||||||
|
updatedItems.remove(at: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let entry = CodableEntry(CachedPeerStoryListHead(items: updatedItems.map { .item($0) }, pinnedIds: [], totalCount: Int32(updatedItems.count), folders: [])) {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
|
||||||
|
if self.folderId == id {
|
||||||
|
var state = self.stateValue
|
||||||
|
state.items.removeAll(where: { itemIds.contains($0.id.id) })
|
||||||
|
self.stateValue = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reorderItemsInFolder(itemIds: [Int32]) {
|
||||||
|
guard let id = self.folderId else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let peerId = self.peerId
|
||||||
|
let _ = (self.account.postbox.transaction { transaction -> Void in
|
||||||
|
let key = ValueBoxKey(length: 8 + 1 + 8)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: 0)
|
||||||
|
key.setInt64(8 + 1, value: id)
|
||||||
|
|
||||||
|
var previousItems: [Stories.Item] = []
|
||||||
|
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
|
||||||
|
previousItems = cached.items.compactMap { item -> Stories.Item? in
|
||||||
|
switch item {
|
||||||
|
case let .item(item):
|
||||||
|
return item
|
||||||
|
case .placeholder:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedItems: [Stories.Item] = []
|
||||||
|
for itemId in itemIds {
|
||||||
|
if let index = previousItems.firstIndex(where: { $0.id == itemId }) {
|
||||||
|
updatedItems.append(previousItems[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for item in previousItems {
|
||||||
|
if !updatedItems.contains(where: { $0.id == item.id }) {
|
||||||
|
updatedItems.append(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let entry = CodableEntry(CachedPeerStoryListHead(items: updatedItems.map { .item($0) }, pinnedIds: [], totalCount: Int32(updatedItems.count), folders: [])) {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
|
||||||
|
if self.folderId == id {
|
||||||
|
var state = self.stateValue
|
||||||
|
|
||||||
|
let previousItems = state.items
|
||||||
|
var updatedItems: [State.Item] = []
|
||||||
|
for itemId in itemIds {
|
||||||
|
if let index = previousItems.firstIndex(where: { $0.id.id == itemId }) {
|
||||||
|
updatedItems.append(previousItems[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for item in previousItems {
|
||||||
|
if !updatedItems.contains(where: { $0.id == item.id }) {
|
||||||
|
updatedItems.append(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.items = updatedItems
|
||||||
|
self.stateValue = state
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var state: Signal<State, NoError> {
|
public var state: Signal<State, NoError> {
|
||||||
|
|
@ -1280,11 +1622,18 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let impl: QueueLocalObject<Impl>
|
private let impl: QueueLocalObject<Impl>
|
||||||
|
|
||||||
public init(account: Account, peerId: EnginePeer.Id, isArchived: Bool) {
|
private let account: Account
|
||||||
|
public let peerId: EnginePeer.Id
|
||||||
|
public let folderId: Int64?
|
||||||
|
|
||||||
|
public init(account: Account, peerId: EnginePeer.Id, isArchived: Bool, folderId: Int64?) {
|
||||||
let queue = Queue.mainQueue()
|
let queue = Queue.mainQueue()
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
self.account = account
|
||||||
|
self.peerId = peerId
|
||||||
|
self.folderId = folderId
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return Impl(queue: queue, account: account, peerId: peerId, isArchived: isArchived)
|
return Impl(queue: queue, account: account, peerId: peerId, isArchived: isArchived, folderId: folderId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1293,6 +1642,111 @@ public final class PeerStoryListContext: StoryListContext {
|
||||||
impl.loadMore(completion : completion)
|
impl.loadMore(completion : completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func addFolder(title: String, completion: @escaping (Int64) -> Void) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
completion(impl.addFolder(title: title))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeFolder(id: Int64) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.removeFolder(id: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func addToFolder(id: Int64, items: [EngineStoryItem]) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.addToFolder(id: id, items: items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeFromFolder(id: Int64, itemIds: [Int32]) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.removeFromFolder(id: id, itemIds: itemIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func reorderItemsInFolder(itemIds: [Int32]) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.reorderItemsInFolder(itemIds: itemIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func folderPreviews(peerId: EnginePeer.Id, account: Account) -> Signal<(peer: PeerReference, [(folder: StoryListContext.State.Folder, item: EngineStoryItem?)]), NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> (peer: PeerReference, [(folder: StoryListContext.State.Folder, item: EngineStoryItem?)]) in
|
||||||
|
let key = ValueBoxKey(length: 8 + 1)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: 0)
|
||||||
|
|
||||||
|
var folders: [StoryListContext.State.Folder] = []
|
||||||
|
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
|
||||||
|
folders = cached.folders
|
||||||
|
}
|
||||||
|
|
||||||
|
var result: [(folder: StoryListContext.State.Folder, item: EngineStoryItem?)] = []
|
||||||
|
|
||||||
|
for folder in folders {
|
||||||
|
let key = ValueBoxKey(length: 8 + 1 + 8)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
key.setInt8(8, value: 0)
|
||||||
|
key.setInt64(8 + 1, value: folder.id)
|
||||||
|
|
||||||
|
var mappedItem: EngineStoryItem?
|
||||||
|
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
|
||||||
|
if let firstItem = cached.items.first, case let .item(item) = firstItem, let media = item.media {
|
||||||
|
mappedItem = EngineStoryItem(
|
||||||
|
id: item.id,
|
||||||
|
timestamp: item.timestamp,
|
||||||
|
expirationTimestamp: item.expirationTimestamp,
|
||||||
|
media: EngineMedia(media),
|
||||||
|
alternativeMediaList: item.alternativeMediaList.map(EngineMedia.init),
|
||||||
|
mediaAreas: item.mediaAreas,
|
||||||
|
text: item.text,
|
||||||
|
entities: item.entities,
|
||||||
|
views: item.views.flatMap { views in
|
||||||
|
return EngineStoryItem.Views(
|
||||||
|
seenCount: views.seenCount,
|
||||||
|
reactedCount: views.reactedCount,
|
||||||
|
forwardCount: views.forwardCount,
|
||||||
|
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
|
||||||
|
return transaction.getPeer(id).flatMap(EnginePeer.init)
|
||||||
|
},
|
||||||
|
reactions: views.reactions,
|
||||||
|
hasList: views.hasList
|
||||||
|
)
|
||||||
|
},
|
||||||
|
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
|
||||||
|
isPinned: item.isPinned,
|
||||||
|
isExpired: item.isExpired,
|
||||||
|
isPublic: item.isPublic,
|
||||||
|
isPending: false,
|
||||||
|
isCloseFriends: item.isCloseFriends,
|
||||||
|
isContacts: item.isContacts,
|
||||||
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
|
myReaction: item.myReaction,
|
||||||
|
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||||
|
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append((folder, mappedItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
let peerReference: PeerReference
|
||||||
|
if let peer = transaction.getPeer(peerId) {
|
||||||
|
peerReference = PeerReference(peer) ?? .user(id: peerId.id._internalGetInt64Value(), accessHash: 0)
|
||||||
|
} else {
|
||||||
|
peerReference = .user(id: peerId.id._internalGetInt64Value(), accessHash: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (peerReference, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class SearchStoryListContext: StoryListContext {
|
public final class SearchStoryListContext: StoryListContext {
|
||||||
|
|
@ -1332,7 +1786,7 @@ public final class SearchStoryListContext: StoryListContext {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.source = source
|
self.source = source
|
||||||
|
|
||||||
self.stateValue = State(peerReference: nil, items: [], availableLanguages: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(""), isCached: false, hasCache: false, allEntityFiles: [:], isLoading: false)
|
self.stateValue = State(peerReference: nil, items: [], availableLanguages: [], availableFolders: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(""), isCached: false, hasCache: false, allEntityFiles: [:], isLoading: false)
|
||||||
self.statePromise.set(.single(self.stateValue))
|
self.statePromise.set(.single(self.stateValue))
|
||||||
|
|
||||||
self.loadMore(completion: nil)
|
self.loadMore(completion: nil)
|
||||||
|
|
@ -2149,7 +2603,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
||||||
|
|
||||||
self.isArchived = isArchived
|
self.isArchived = isArchived
|
||||||
|
|
||||||
self.stateValue = State(peerReference: nil, items: [], availableLanguages: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: false, allEntityFiles: [:], isLoading: false)
|
self.stateValue = State(peerReference: nil, items: [], availableLanguages: [], availableFolders: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: false, allEntityFiles: [:], isLoading: false)
|
||||||
|
|
||||||
let localStateKey: PostboxViewKey = .storiesState(key: .local)
|
let localStateKey: PostboxViewKey = .storiesState(key: .local)
|
||||||
|
|
||||||
|
|
@ -2166,6 +2620,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
||||||
peerReference: peer.flatMap(PeerReference.init),
|
peerReference: peer.flatMap(PeerReference.init),
|
||||||
items: [],
|
items: [],
|
||||||
availableLanguages: [],
|
availableLanguages: [],
|
||||||
|
availableFolders: [],
|
||||||
pinnedIds: [],
|
pinnedIds: [],
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
loadMoreToken: AnyHashable(0),
|
loadMoreToken: AnyHashable(0),
|
||||||
|
|
@ -2313,6 +2768,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
||||||
peerReference: (peer?._asPeer()).flatMap(PeerReference.init),
|
peerReference: (peer?._asPeer()).flatMap(PeerReference.init),
|
||||||
items: items,
|
items: items,
|
||||||
availableLanguages: availableLanguages,
|
availableLanguages: availableLanguages,
|
||||||
|
availableFolders: [],
|
||||||
pinnedIds: [],
|
pinnedIds: [],
|
||||||
totalCount: items.count,
|
totalCount: items.count,
|
||||||
loadMoreToken: nil,
|
loadMoreToken: nil,
|
||||||
|
|
@ -2413,6 +2869,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
||||||
peerReference: PeerReference(peer),
|
peerReference: PeerReference(peer),
|
||||||
items: items,
|
items: items,
|
||||||
availableLanguages: [],
|
availableLanguages: [],
|
||||||
|
availableFolders: [],
|
||||||
pinnedIds: [],
|
pinnedIds: [],
|
||||||
totalCount: items.count,
|
totalCount: items.count,
|
||||||
loadMoreToken: nil,
|
loadMoreToken: nil,
|
||||||
|
|
@ -2592,6 +3049,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
||||||
peerReference: self.stateValue.peerReference,
|
peerReference: self.stateValue.peerReference,
|
||||||
items: items,
|
items: items,
|
||||||
availableLanguages: [],
|
availableLanguages: [],
|
||||||
|
availableFolders: [],
|
||||||
pinnedIds: [],
|
pinnedIds: [],
|
||||||
totalCount: items.count,
|
totalCount: items.count,
|
||||||
loadMoreToken: nil,
|
loadMoreToken: nil,
|
||||||
|
|
|
||||||
|
|
@ -720,7 +720,7 @@ public extension StarGift {
|
||||||
extension StarGift {
|
extension StarGift {
|
||||||
init?(apiStarGift: Api.StarGift) {
|
init?(apiStarGift: Api.StarGift) {
|
||||||
switch apiStarGift {
|
switch apiStarGift {
|
||||||
case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title, releasedBy):
|
case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title, releasedBy, _, _):
|
||||||
var flags = StarGift.Gift.Flags()
|
var flags = StarGift.Gift.Flags()
|
||||||
if (apiFlags & (1 << 2)) != 0 {
|
if (apiFlags & (1 << 2)) != 0 {
|
||||||
flags.insert(.isBirthdayGift)
|
flags.insert(.isBirthdayGift)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ swift_library(
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/TooltipUI",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/UIKitRuntimeUtils",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -5,44 +5,62 @@ import ComponentFlow
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import TextLoadingEffect
|
import TextLoadingEffect
|
||||||
import ComponentDisplayAdapters
|
import ComponentDisplayAdapters
|
||||||
|
import TooltipUI
|
||||||
|
import AccountContext
|
||||||
|
import UIKitRuntimeUtils
|
||||||
|
|
||||||
public final class PeerInfoRatingComponent: Component {
|
public final class PeerInfoRatingComponent: Component {
|
||||||
|
let context: AccountContext
|
||||||
let backgroundColor: UIColor
|
let backgroundColor: UIColor
|
||||||
let foregroundColor: UIColor
|
let foregroundColor: UIColor
|
||||||
|
let tooltipBackgroundColor: UIColor
|
||||||
let isExpanded: Bool
|
let isExpanded: Bool
|
||||||
let compactLabel: String
|
let compactLabel: String
|
||||||
let fraction: CGFloat
|
let fraction: CGFloat
|
||||||
let label: String
|
let label: String
|
||||||
let nextLabel: String
|
let nextLabel: String
|
||||||
|
let tooltipLabel: String
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
context: AccountContext,
|
||||||
backgroundColor: UIColor,
|
backgroundColor: UIColor,
|
||||||
foregroundColor: UIColor,
|
foregroundColor: UIColor,
|
||||||
|
tooltipBackgroundColor: UIColor,
|
||||||
isExpanded: Bool,
|
isExpanded: Bool,
|
||||||
compactLabel: String,
|
compactLabel: String,
|
||||||
fraction: CGFloat,
|
fraction: CGFloat,
|
||||||
label: String,
|
label: String,
|
||||||
nextLabel: String,
|
nextLabel: String,
|
||||||
|
tooltipLabel: String,
|
||||||
action: @escaping () -> Void
|
action: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
|
self.context = context
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.foregroundColor = foregroundColor
|
self.foregroundColor = foregroundColor
|
||||||
|
self.tooltipBackgroundColor = tooltipBackgroundColor
|
||||||
self.isExpanded = isExpanded
|
self.isExpanded = isExpanded
|
||||||
self.compactLabel = compactLabel
|
self.compactLabel = compactLabel
|
||||||
self.fraction = fraction
|
self.fraction = fraction
|
||||||
self.label = label
|
self.label = label
|
||||||
self.nextLabel = nextLabel
|
self.nextLabel = nextLabel
|
||||||
|
self.tooltipLabel = tooltipLabel
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PeerInfoRatingComponent, rhs: PeerInfoRatingComponent) -> Bool {
|
public static func ==(lhs: PeerInfoRatingComponent, rhs: PeerInfoRatingComponent) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.backgroundColor != rhs.backgroundColor {
|
if lhs.backgroundColor != rhs.backgroundColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.foregroundColor != rhs.foregroundColor {
|
if lhs.foregroundColor != rhs.foregroundColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.tooltipBackgroundColor != rhs.tooltipBackgroundColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.isExpanded != rhs.isExpanded {
|
if lhs.isExpanded != rhs.isExpanded {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +76,9 @@ public final class PeerInfoRatingComponent: Component {
|
||||||
if lhs.nextLabel != rhs.nextLabel {
|
if lhs.nextLabel != rhs.nextLabel {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.tooltipLabel != rhs.tooltipLabel {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,6 +98,8 @@ public final class PeerInfoRatingComponent: Component {
|
||||||
|
|
||||||
private var component: PeerInfoRatingComponent?
|
private var component: PeerInfoRatingComponent?
|
||||||
|
|
||||||
|
private var tooltipController: TooltipScreen?
|
||||||
|
|
||||||
override public init(frame: CGRect) {
|
override public init(frame: CGRect) {
|
||||||
self.backgroundView = UIImageView()
|
self.backgroundView = UIImageView()
|
||||||
|
|
||||||
|
|
@ -261,6 +284,50 @@ public final class PeerInfoRatingComponent: Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tooltipController: TooltipScreen
|
||||||
|
if let current = self.tooltipController {
|
||||||
|
tooltipController = current
|
||||||
|
} else {
|
||||||
|
tooltipController = TooltipScreen(
|
||||||
|
context: component.context,
|
||||||
|
account: component.context.account,
|
||||||
|
sharedContext: component.context.sharedContext,
|
||||||
|
text: .attributedString(text: NSAttributedString(string: component.tooltipLabel, font: Font.semibold(11.0), textColor: .white)),
|
||||||
|
style: .customBlur(component.tooltipBackgroundColor, -4.0),
|
||||||
|
arrowStyle: .small,
|
||||||
|
location: .point(CGRect(origin: CGPoint(x: 100.0, y: 100.0), size: CGSize()), .bottom),
|
||||||
|
displayDuration: .infinite,
|
||||||
|
isShimmering: true,
|
||||||
|
cornerRadius: 10.0,
|
||||||
|
shouldDismissOnTouch: { _, _ in
|
||||||
|
return .ignore
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.tooltipController = tooltipController
|
||||||
|
|
||||||
|
tooltipController.containerLayoutUpdated(ContainerViewLayout(
|
||||||
|
size: CGSize(width: 200.0, height: 200.0),
|
||||||
|
metrics: LayoutMetrics(),
|
||||||
|
deviceMetrics: DeviceMetrics.iPhoneXSMax,
|
||||||
|
intrinsicInsets: UIEdgeInsets(),
|
||||||
|
safeInsets: UIEdgeInsets(),
|
||||||
|
additionalInsets: UIEdgeInsets(),
|
||||||
|
statusBarHeight: nil,
|
||||||
|
inputHeight: nil,
|
||||||
|
inputHeightIsInteractivellyChanging: false,
|
||||||
|
inVoiceOver: false
|
||||||
|
), transition: .immediate)
|
||||||
|
|
||||||
|
self.layer.addSublayer(tooltipController.view.layer)
|
||||||
|
tooltipController.viewWillAppear(false)
|
||||||
|
tooltipController.viewDidAppear(false)
|
||||||
|
tooltipController.setIgnoreAppearanceMethodInvocations(true)
|
||||||
|
tooltipController.view.isUserInteractionEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: tooltipController.view, frame: CGRect(origin: CGPoint(), size: CGSize(width: 200.0, height: 200.0)).offsetBy(dx: -200.0 * 0.5 + foregroundFrame.width - 7.0, dy: -200.0 * 0.5))
|
||||||
|
alphaTransition.setAlpha(view: tooltipController.view, alpha: component.isExpanded ? 1.0 : 0.0)
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -677,7 +677,7 @@ public func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, c
|
||||||
|
|
||||||
if case .user = inputData {
|
if case .user = inputData {
|
||||||
signals.append(Signal { _ in
|
signals.append(Signal { _ in
|
||||||
let listContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
|
let listContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
|
||||||
let expiringListContext = PeerExpiringStoryListContext(account: context.account, peerId: peerId)
|
let expiringListContext = PeerExpiringStoryListContext(account: context.account, peerId: peerId)
|
||||||
|
|
||||||
return ActionDisposable {
|
return ActionDisposable {
|
||||||
|
|
@ -829,7 +829,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
||||||
)
|
)
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
|
||||||
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
|
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
|
||||||
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|
||||||
|> map { state -> Bool? in
|
|> map { state -> Bool? in
|
||||||
if !state.hasCache {
|
if !state.hasCache {
|
||||||
|
|
@ -1188,7 +1188,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||||
secretChatKeyFingerprint = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.SecretChatKeyFingerprint(id: secretChatId))
|
secretChatKeyFingerprint = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.SecretChatKeyFingerprint(id: secretChatId))
|
||||||
}
|
}
|
||||||
|
|
||||||
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
|
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
|
||||||
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|
||||||
|> map { state -> Bool? in
|
|> map { state -> Bool? in
|
||||||
if !state.hasCache {
|
if !state.hasCache {
|
||||||
|
|
@ -1201,7 +1201,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||||
let hasStoryArchive: Signal<Bool?, NoError>
|
let hasStoryArchive: Signal<Bool?, NoError>
|
||||||
var storyArchiveListContext: StoryListContext?
|
var storyArchiveListContext: StoryListContext?
|
||||||
if isMyProfile {
|
if isMyProfile {
|
||||||
let storyArchiveListContextValue = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: true)
|
let storyArchiveListContextValue = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: true, folderId: nil)
|
||||||
storyArchiveListContext = storyArchiveListContextValue
|
storyArchiveListContext = storyArchiveListContextValue
|
||||||
hasStoryArchive = storyArchiveListContextValue.state
|
hasStoryArchive = storyArchiveListContextValue.state
|
||||||
|> map { state -> Bool? in
|
|> map { state -> Bool? in
|
||||||
|
|
@ -1535,7 +1535,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||||
let requestsContextPromise = Promise<PeerInvitationImportersContext?>(nil)
|
let requestsContextPromise = Promise<PeerInvitationImportersContext?>(nil)
|
||||||
let requestsStatePromise = Promise<PeerInvitationImportersState?>(nil)
|
let requestsStatePromise = Promise<PeerInvitationImportersState?>(nil)
|
||||||
|
|
||||||
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
|
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
|
||||||
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|
||||||
|> map { state -> Bool? in
|
|> map { state -> Bool? in
|
||||||
if !state.hasCache {
|
if !state.hasCache {
|
||||||
|
|
@ -1857,7 +1857,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||||
let storyListContext: StoryListContext?
|
let storyListContext: StoryListContext?
|
||||||
let hasStories: Signal<Bool?, NoError>
|
let hasStories: Signal<Bool?, NoError>
|
||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
|
storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
|
||||||
hasStories = storyListContext!.state
|
hasStories = storyListContext!.state
|
||||||
|> map { state -> Bool? in
|
|> map { state -> Bool? in
|
||||||
if !state.hasCache {
|
if !state.hasCache {
|
||||||
|
|
|
||||||
|
|
@ -1926,22 +1926,47 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||||
self.subtitleRating = subtitleRating
|
self.subtitleRating = subtitleRating
|
||||||
}
|
}
|
||||||
let fraction: CGFloat
|
let fraction: CGFloat
|
||||||
|
let tooltipLabel: String
|
||||||
if let nextLevelStars = starRating.nextLevelStars {
|
if let nextLevelStars = starRating.nextLevelStars {
|
||||||
fraction = CGFloat(starRating.currentLevelStars) / CGFloat(nextLevelStars)
|
fraction = CGFloat(starRating.currentLevelStars) / CGFloat(nextLevelStars)
|
||||||
|
tooltipLabel = "\(starRating.currentLevelStars) / \(nextLevelStars)"
|
||||||
} else {
|
} else {
|
||||||
fraction = 1.0
|
fraction = 1.0
|
||||||
|
tooltipLabel = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tooltipBackgroundColor: UIColor
|
||||||
|
let ratingBackgroundColor: UIColor
|
||||||
|
let ratingForegroundColor: UIColor
|
||||||
|
|
||||||
|
if peer?.profileColor != nil {
|
||||||
|
ratingBackgroundColor = UIColor(white: 1.0, alpha: 0.1)
|
||||||
|
ratingForegroundColor = UIColor(white: 1.0, alpha: 1.0)
|
||||||
|
if !self.isAvatarExpanded {
|
||||||
|
tooltipBackgroundColor = contentButtonBackgroundColor
|
||||||
|
} else {
|
||||||
|
tooltipBackgroundColor = UIColor(rgb: 0x000000, alpha: 0.65)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ratingBackgroundColor = presentationData.theme.list.freeTextColor.withMultipliedAlpha(0.1)
|
||||||
|
ratingForegroundColor = presentationData.theme.list.freeTextColor.withMultipliedAlpha(1.0)
|
||||||
|
tooltipBackgroundColor = UIColor(rgb: 0x000000, alpha: 0.65)
|
||||||
|
}
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
subtitleRatingSize = subtitleRating.update(
|
subtitleRatingSize = subtitleRating.update(
|
||||||
transition: subtitleRatingTransition,
|
transition: subtitleRatingTransition,
|
||||||
component: AnyComponent(PeerInfoRatingComponent(
|
component: AnyComponent(PeerInfoRatingComponent(
|
||||||
backgroundColor: UIColor(white: 1.0, alpha: 0.1),
|
context: self.context,
|
||||||
foregroundColor: UIColor(white: 1.0, alpha: 1.0),
|
backgroundColor: ratingBackgroundColor,
|
||||||
|
foregroundColor: ratingForegroundColor,
|
||||||
|
tooltipBackgroundColor: tooltipBackgroundColor,
|
||||||
isExpanded: self.subtitleRatingIsExpanded,
|
isExpanded: self.subtitleRatingIsExpanded,
|
||||||
compactLabel: "\(starRating.level)",
|
compactLabel: "\(starRating.level)",
|
||||||
fraction: fraction,
|
fraction: fraction,
|
||||||
label: "Level \(starRating.level)",
|
label: "Level \(starRating.level)",
|
||||||
nextLabel: starRating.nextLevelStars != nil ? "\(starRating.level + 1)" : "",
|
nextLabel: starRating.nextLevelStars != nil ? "\(starRating.level + 1)" : "",
|
||||||
|
tooltipLabel: tooltipLabel,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
|
@ -1994,7 +2019,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||||
let rawSubtitleFrame = CGRect(origin: CGPoint(x: subtitleCenter.x - subtitleFrame.size.width / 2.0, y: subtitleCenter.y - subtitleFrame.size.height / 2.0), size: subtitleFrame.size)
|
let rawSubtitleFrame = CGRect(origin: CGPoint(x: subtitleCenter.x - subtitleFrame.size.width / 2.0, y: subtitleCenter.y - subtitleFrame.size.height / 2.0), size: subtitleFrame.size)
|
||||||
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
|
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
|
||||||
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize()))
|
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize()))
|
||||||
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize()))
|
transition.updatePosition(node: self.subtitleNode, position: CGPoint(x: 0.0, y: subtitleOffset))
|
||||||
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset - 1.0), size: CGSize()))
|
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset - 1.0), size: CGSize()))
|
||||||
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||||
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
|
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
|
||||||
|
|
@ -2006,6 +2031,17 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||||
transition.updateFrameAdditive(view: subtitleBadgeView, frame: subtitleBadgeFrame)
|
transition.updateFrameAdditive(view: subtitleBadgeView, frame: subtitleBadgeFrame)
|
||||||
transition.updateAlpha(layer: subtitleBadgeView.layer, alpha: (1.0 - transitionFraction))
|
transition.updateAlpha(layer: subtitleBadgeView.layer, alpha: (1.0 - transitionFraction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let subtitleRatingView = self.subtitleRating?.view, let subtitleRatingSize {
|
||||||
|
let subtitleBadgeFrame: CGRect
|
||||||
|
if self.subtitleRatingIsExpanded {
|
||||||
|
subtitleBadgeFrame = CGRect(origin: CGPoint(x: -subtitleRatingSize.width * 0.5, y: floor((-subtitleRatingSize.height) * 0.5)), size: subtitleRatingSize)
|
||||||
|
} else {
|
||||||
|
subtitleBadgeFrame = CGRect(origin: CGPoint(x: (-subtitleSize.width) * 0.5 - 4.0 - subtitleRatingSize.width, y: floor((-subtitleRatingSize.height) * 0.5)), size: subtitleRatingSize)
|
||||||
|
}
|
||||||
|
transition.updateFrameAdditive(view: subtitleRatingView, frame: subtitleBadgeFrame)
|
||||||
|
transition.updateAlpha(layer: subtitleRatingView.layer, alpha: (1.0 - transitionFraction))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let titleScale: CGFloat
|
let titleScale: CGFloat
|
||||||
let subtitleScale: CGFloat
|
let subtitleScale: CGFloat
|
||||||
|
|
@ -2048,7 +2084,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||||
usernameCenter.x = rawTitleFrame.center.x + (usernameCenter.x - rawTitleFrame.center.x) * subtitleScale
|
usernameCenter.x = rawTitleFrame.center.x + (usernameCenter.x - rawTitleFrame.center.x) * subtitleScale
|
||||||
transition.updateFrameAdditiveToCenter(node: self.usernameNodeContainer, frame: CGRect(origin: usernameCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
|
transition.updateFrameAdditiveToCenter(node: self.usernameNodeContainer, frame: CGRect(origin: usernameCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize()))
|
transition.updatePosition(node: self.subtitleNode, position: CGPoint(x: 0.0, y: subtitleOffset))
|
||||||
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset - 1.0), size: CGSize()))
|
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset - 1.0), size: CGSize()))
|
||||||
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||||
transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale)
|
transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale)
|
||||||
|
|
@ -2072,7 +2108,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||||
transition.updateAlpha(layer: subtitleRatingView.layer, alpha: (1.0 - transitionFraction) * subtitleRatingFraction)
|
transition.updateAlpha(layer: subtitleRatingView.layer, alpha: (1.0 - transitionFraction) * subtitleRatingFraction)
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateAlpha(node: self.subtitleNode, alpha: self.subtitleRatingIsExpanded ? 0.0 : 1.0)
|
let subtitleAlpha: CGFloat = subtitleRatingFraction * (self.subtitleRatingIsExpanded ? 0.0 : 1.0) + (1.0 - subtitleRatingFraction) * 1.0
|
||||||
|
let subtitleInnerScale: CGFloat = subtitleRatingFraction * (self.subtitleRatingIsExpanded ? 0.001 : 1.0) + (1.0 - subtitleRatingFraction) * 1.0
|
||||||
|
transition.updateAlpha(node: self.subtitleNode, alpha: subtitleAlpha)
|
||||||
|
transition.updateTransformScale(node: self.subtitleNode, scale: subtitleInnerScale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2600,6 +2639,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||||
self.isAvatarExpanded = isAvatarExpanded
|
self.isAvatarExpanded = isAvatarExpanded
|
||||||
if isAvatarExpanded {
|
if isAvatarExpanded {
|
||||||
self.avatarListNode.listContainerNode.selectFirstItem()
|
self.avatarListNode.listContainerNode.selectFirstItem()
|
||||||
|
self.subtitleRatingIsExpanded = false
|
||||||
}
|
}
|
||||||
if case .animated = transition, !isAvatarExpanded {
|
if case .animated = transition, !isAvatarExpanded {
|
||||||
self.avatarListNode.animateAvatarCollapse(transition: transition)
|
self.avatarListNode.animateAvatarCollapse(transition: transition)
|
||||||
|
|
|
||||||
|
|
@ -11496,26 +11496,150 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||||
private func displayMediaGalleryContextMenu(source: ContextReferenceContentNode, gesture: ContextGesture?) {
|
private func displayMediaGalleryContextMenu(source: ContextReferenceContentNode, gesture: ContextGesture?) {
|
||||||
let peerId = self.peerId
|
let peerId = self.peerId
|
||||||
|
|
||||||
if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .botPreview = currentPaneKey {
|
var isBotPreviewOrStories = false
|
||||||
|
if let currentPaneKey = self.paneContainerNode.currentPaneKey {
|
||||||
|
if case .botPreview = currentPaneKey {
|
||||||
|
isBotPreviewOrStories = true
|
||||||
|
} else if case .stories = currentPaneKey {
|
||||||
|
isBotPreviewOrStories = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isBotPreviewOrStories {
|
||||||
guard let controller = self.controller else {
|
guard let controller = self.controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoStoryPaneNode else {
|
guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoStoryPaneNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let data = self.data, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
if case .botPreview = pane.scope {
|
||||||
|
guard let data = self.data, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) else {
|
||||||
let strings = self.presentationData.strings
|
return
|
||||||
|
}
|
||||||
var ignoreNextActions = false
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
if pane.canAddMoreBotPreviews() {
|
|
||||||
items.append(.action(ContextMenuActionItem(text: strings.BotPreviews_MenuAddPreview, icon: { theme in
|
let strings = self.presentationData.strings
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
|
||||||
|
var ignoreNextActions = false
|
||||||
|
|
||||||
|
if pane.canAddMoreBotPreviews() {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strings.BotPreviews_MenuAddPreview, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { [weak self] _, a in
|
||||||
|
if ignoreNextActions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ignoreNextActions = true
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if let self {
|
||||||
|
self.headerNode.navigationButtonContainer.performAction?(.postStory, nil, nil)
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
if pane.canReorder() {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { [weak pane] _, a in
|
||||||
|
if ignoreNextActions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ignoreNextActions = true
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if let pane {
|
||||||
|
pane.beginReordering()
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { [weak self] _, a in
|
||||||
|
if ignoreNextActions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ignoreNextActions = true
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if let self {
|
||||||
|
self.toggleStorySelection(ids: [], isSelected: true)
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
|
if let language = pane.currentBotPreviewLanguage {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
|
}, action: { [weak pane] _, a in
|
||||||
|
if ignoreNextActions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ignoreNextActions = true
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if let pane {
|
||||||
|
pane.presentDeleteBotPreviewLanguage()
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
|
contextController.passthroughTouchEvent = { [weak self] sourceView, point in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return .ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil)
|
||||||
|
guard let localResult = strongSelf.hitTest(localPoint, with: nil) else {
|
||||||
|
return .dismiss(consume: true, result: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testView: UIView? = localResult
|
||||||
|
while true {
|
||||||
|
if let testViewValue = testView {
|
||||||
|
if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton {
|
||||||
|
node.isUserInteractionEnabled = false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
node.isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
return .dismiss(consume: false, result: nil)
|
||||||
|
} else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode {
|
||||||
|
node.brieflyDisableTouchActions()
|
||||||
|
return .dismiss(consume: false, result: nil)
|
||||||
|
} else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode {
|
||||||
|
node.brieflyDisableTouchActions()
|
||||||
|
return .dismiss(consume: false, result: nil)
|
||||||
|
} else {
|
||||||
|
testView = testViewValue.superview
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .dismiss(consume: true, result: nil)
|
||||||
|
}
|
||||||
|
self.mediaGalleryContextMenu = contextController
|
||||||
|
controller.presentInGlobalOverlay(contextController)
|
||||||
|
} else if case .peer = pane.scope {
|
||||||
|
guard let data = self.data, let user = data.peer as? TelegramUser else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = user
|
||||||
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
|
let strings = self.presentationData.strings
|
||||||
|
let _ = strings
|
||||||
|
|
||||||
|
var ignoreNextActions = false
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Add Stories", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddStoryIcon"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
if ignoreNextActions {
|
if ignoreNextActions {
|
||||||
return
|
return
|
||||||
|
|
@ -11527,92 +11651,96 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||||
self.headerNode.navigationButtonContainer.performAction?(.postStory, nil, nil)
|
self.headerNode.navigationButtonContainer.performAction?(.postStory, nil, nil)
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
}
|
|
||||||
|
|
||||||
if pane.canReorder() {
|
|
||||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak pane] _, a in
|
|
||||||
if ignoreNextActions {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ignoreNextActions = true
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
if let pane {
|
|
||||||
pane.beginReordering()
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak self] _, a in
|
|
||||||
if ignoreNextActions {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ignoreNextActions = true
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
if let self {
|
if pane.canReorder() {
|
||||||
self.toggleStorySelection(ids: [], isSelected: true)
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in
|
||||||
}
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
||||||
})))
|
}, action: { [weak pane] _, a in
|
||||||
|
if ignoreNextActions {
|
||||||
if let language = pane.currentBotPreviewLanguage {
|
return
|
||||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in
|
}
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
ignoreNextActions = true
|
||||||
}, action: { [weak pane] _, a in
|
a(.default)
|
||||||
if ignoreNextActions {
|
|
||||||
return
|
if let pane {
|
||||||
}
|
pane.beginReordering()
|
||||||
ignoreNextActions = true
|
}
|
||||||
a(.default)
|
})))
|
||||||
|
|
||||||
if let pane {
|
|
||||||
pane.presentDeleteBotPreviewLanguage()
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
|
||||||
contextController.passthroughTouchEvent = { [weak self] sourceView, point in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return .ignore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil)
|
if let folder = pane.currentStoryFolder {
|
||||||
guard let localResult = strongSelf.hitTest(localPoint, with: nil) else {
|
let _ = folder
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Delete Album", textColor: .destructive, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
|
}, action: { [weak pane] _, a in
|
||||||
|
if ignoreNextActions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ignoreNextActions = true
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if let pane {
|
||||||
|
pane.presentDeleteCurrentStoryFolder()
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let language = pane.currentBotPreviewLanguage {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
|
}, action: { [weak pane] _, a in
|
||||||
|
if ignoreNextActions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ignoreNextActions = true
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if let pane {
|
||||||
|
pane.presentDeleteBotPreviewLanguage()
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
|
contextController.passthroughTouchEvent = { [weak self] sourceView, point in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return .ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil)
|
||||||
|
guard let localResult = strongSelf.hitTest(localPoint, with: nil) else {
|
||||||
|
return .dismiss(consume: true, result: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testView: UIView? = localResult
|
||||||
|
while true {
|
||||||
|
if let testViewValue = testView {
|
||||||
|
if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton {
|
||||||
|
node.isUserInteractionEnabled = false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
node.isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
return .dismiss(consume: false, result: nil)
|
||||||
|
} else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode {
|
||||||
|
node.brieflyDisableTouchActions()
|
||||||
|
return .dismiss(consume: false, result: nil)
|
||||||
|
} else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode {
|
||||||
|
node.brieflyDisableTouchActions()
|
||||||
|
return .dismiss(consume: false, result: nil)
|
||||||
|
} else {
|
||||||
|
testView = testViewValue.superview
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return .dismiss(consume: true, result: nil)
|
return .dismiss(consume: true, result: nil)
|
||||||
}
|
}
|
||||||
|
self.mediaGalleryContextMenu = contextController
|
||||||
var testView: UIView? = localResult
|
controller.presentInGlobalOverlay(contextController)
|
||||||
while true {
|
|
||||||
if let testViewValue = testView {
|
|
||||||
if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton {
|
|
||||||
node.isUserInteractionEnabled = false
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
node.isUserInteractionEnabled = true
|
|
||||||
}
|
|
||||||
return .dismiss(consume: false, result: nil)
|
|
||||||
} else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode {
|
|
||||||
node.brieflyDisableTouchActions()
|
|
||||||
return .dismiss(consume: false, result: nil)
|
|
||||||
} else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode {
|
|
||||||
node.brieflyDisableTouchActions()
|
|
||||||
return .dismiss(consume: false, result: nil)
|
|
||||||
} else {
|
|
||||||
testView = testViewValue.superview
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .dismiss(consume: true, result: nil)
|
|
||||||
}
|
}
|
||||||
self.mediaGalleryContextMenu = contextController
|
|
||||||
controller.presentInGlobalOverlay(contextController)
|
|
||||||
} else {
|
} else {
|
||||||
let _ = (self.context.engine.data.get(EngineDataMap([
|
let _ = (self.context.engine.data.get(EngineDataMap([
|
||||||
TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photo),
|
TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photo),
|
||||||
|
|
@ -12482,6 +12610,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||||
if let data = self.data, data.hasBotPreviewItems, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
if let data = self.data, data.hasBotPreviewItems, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true))
|
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true))
|
||||||
}
|
}
|
||||||
|
case .stories:
|
||||||
|
if let data = self.data, data.peer?.id == self.context.account.peerId {
|
||||||
|
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true))
|
||||||
|
}
|
||||||
case .gifts:
|
case .gifts:
|
||||||
//if let data = self.data, let channel = data.peer as? TelegramChannel, case .broadcast = channel.info {
|
//if let data = self.data, let channel = data.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .sort, isForExpandedView: true))
|
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .sort, isForExpandedView: true))
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,18 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let peerId: EnginePeer.Id
|
let peerId: EnginePeer.Id
|
||||||
let scope: PeerInfoStoryGridScreen.Scope
|
let scope: PeerInfoStoryGridScreen.Scope
|
||||||
|
let selectionModeCompletion: (([EngineStoryItem]) -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
peerId: EnginePeer.Id,
|
peerId: EnginePeer.Id,
|
||||||
scope: PeerInfoStoryGridScreen.Scope
|
scope: PeerInfoStoryGridScreen.Scope,
|
||||||
|
selectionModeCompletion: (([EngineStoryItem]) -> Void)?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
|
self.selectionModeCompletion = selectionModeCompletion
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: PeerInfoStoryGridScreenComponent, rhs: PeerInfoStoryGridScreenComponent) -> Bool {
|
static func ==(lhs: PeerInfoStoryGridScreenComponent, rhs: PeerInfoStoryGridScreenComponent) -> Bool {
|
||||||
|
|
@ -342,11 +345,24 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let buttonText: String
|
let buttonText: String
|
||||||
switch component.scope {
|
var buttonIsEnabled = true
|
||||||
case .saved:
|
if component.selectionModeCompletion != nil {
|
||||||
buttonText = self.selectedCount > 0 ? environment.strings.ChatList_Context_Archive : environment.strings.StoryList_SavedAddAction
|
//TODO:localize
|
||||||
case .archive:
|
if self.selectedCount == 0 {
|
||||||
buttonText = environment.strings.StoryList_SaveToProfile
|
buttonText = "Add Stories"
|
||||||
|
buttonIsEnabled = false
|
||||||
|
} else if self.selectedCount == 1 {
|
||||||
|
buttonText = "Add 1 Story"
|
||||||
|
} else {
|
||||||
|
buttonText = "Add \(self.selectedCount) Stories"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch component.scope {
|
||||||
|
case .saved:
|
||||||
|
buttonText = self.selectedCount > 0 ? environment.strings.ChatList_Context_Archive : environment.strings.StoryList_SavedAddAction
|
||||||
|
case .archive:
|
||||||
|
buttonText = environment.strings.StoryList_SaveToProfile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectionPanelSize = selectionPanel.update(
|
let selectionPanelSize = selectionPanel.update(
|
||||||
|
|
@ -355,7 +371,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
title: buttonText,
|
title: buttonText,
|
||||||
label: nil,
|
label: nil,
|
||||||
isEnabled: true,
|
isEnabled: buttonIsEnabled,
|
||||||
insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: environment.safeInsets.bottom, right: sideInset),
|
insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: environment.safeInsets.bottom, right: sideInset),
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self, let component = self.component, let environment = self.environment else {
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
|
|
@ -365,6 +381,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let selectionModeCompletion = component.selectionModeCompletion {
|
||||||
|
selectionModeCompletion(Array(paneNode.selectedItems.values))
|
||||||
|
environment.controller()?.dismiss()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId))
|
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
guard let self, let peer else {
|
guard let self, let peer else {
|
||||||
|
|
@ -522,6 +544,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||||
}
|
}
|
||||||
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
|
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if component.selectionModeCompletion != nil {
|
||||||
|
paneNode.shouldOpenItemsWhileInSelectionMode = false
|
||||||
|
paneNode.setIsSelectionModeActive(true)
|
||||||
|
}
|
||||||
|
|
||||||
applyState = true
|
applyState = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -562,6 +590,7 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let scope: Scope
|
private let scope: Scope
|
||||||
|
private let selectionModeCompletion: (([EngineStoryItem]) -> Void)?
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
|
|
||||||
private var titleView: ChatTitleView?
|
private var titleView: ChatTitleView?
|
||||||
|
|
@ -573,15 +602,18 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
peerId: EnginePeer.Id,
|
peerId: EnginePeer.Id,
|
||||||
scope: Scope
|
scope: Scope,
|
||||||
|
selectionModeCompletion: (([EngineStoryItem]) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
|
self.selectionModeCompletion = selectionModeCompletion
|
||||||
|
|
||||||
super.init(context: context, component: PeerInfoStoryGridScreenComponent(
|
super.init(context: context, component: PeerInfoStoryGridScreenComponent(
|
||||||
context: context,
|
context: context,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
scope: scope
|
scope: scope,
|
||||||
|
selectionModeCompletion: selectionModeCompletion
|
||||||
), navigationBarAppearance: .default, theme: .default)
|
), navigationBarAppearance: .default, theme: .default)
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
|
|
@ -636,49 +668,54 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
||||||
func updateTitle() {
|
func updateTitle() {
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
switch self.scope {
|
if self.selectionModeCompletion != nil {
|
||||||
case .saved:
|
//TODO:localize
|
||||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View, let paneNode = componentView.paneNode else {
|
self.titleView?.titleContent = .custom("Add Stories", nil, false)
|
||||||
return
|
} else {
|
||||||
}
|
switch self.scope {
|
||||||
let title: String?
|
case .saved:
|
||||||
if componentView.selectedCount != 0 {
|
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View, let paneNode = componentView.paneNode else {
|
||||||
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
return
|
||||||
} else if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty {
|
}
|
||||||
title = paneStatusText
|
let title: String?
|
||||||
} else {
|
if componentView.selectedCount != 0 {
|
||||||
title = nil
|
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
||||||
}
|
} else if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty {
|
||||||
self.titleView?.titleContent = .custom(presentationData.strings.StoryList_TitleSaved, title, false)
|
title = paneStatusText
|
||||||
|
} else {
|
||||||
if paneNode.isSelectionModeActive {
|
title = nil
|
||||||
self.navigationItem.setRightBarButton(self.doneBarButtonItem, animated: false)
|
}
|
||||||
} else {
|
self.titleView?.titleContent = .custom(presentationData.strings.StoryList_TitleSaved, title, false)
|
||||||
self.navigationItem.setRightBarButton(self.moreBarButtonItem, animated: false)
|
|
||||||
}
|
if paneNode.isSelectionModeActive {
|
||||||
case .archive:
|
self.navigationItem.setRightBarButton(self.doneBarButtonItem, animated: false)
|
||||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
} else {
|
||||||
return
|
self.navigationItem.setRightBarButton(self.moreBarButtonItem, animated: false)
|
||||||
}
|
}
|
||||||
let title: String
|
case .archive:
|
||||||
if componentView.selectedCount != 0 {
|
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
||||||
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
return
|
||||||
} else {
|
}
|
||||||
title = presentationData.strings.StoryList_TitleArchive
|
let title: String
|
||||||
}
|
if componentView.selectedCount != 0 {
|
||||||
self.titleView?.titleContent = .custom(title, nil, false)
|
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
||||||
|
} else {
|
||||||
var hasMenu = false
|
title = presentationData.strings.StoryList_TitleArchive
|
||||||
if componentView.selectedCount != 0 {
|
}
|
||||||
hasMenu = true
|
self.titleView?.titleContent = .custom(title, nil, false)
|
||||||
} else if let paneNode = componentView.paneNode, !paneNode.isEmpty {
|
|
||||||
hasMenu = true
|
var hasMenu = false
|
||||||
}
|
if componentView.selectedCount != 0 {
|
||||||
|
hasMenu = true
|
||||||
if hasMenu {
|
} else if let paneNode = componentView.paneNode, !paneNode.isEmpty {
|
||||||
self.navigationItem.setRightBarButton(self.moreBarButtonItem, animated: false)
|
hasMenu = true
|
||||||
} else {
|
}
|
||||||
self.navigationItem.setRightBarButton(nil, animated: false)
|
|
||||||
|
if hasMenu {
|
||||||
|
self.navigationItem.setRightBarButton(self.moreBarButtonItem, animated: false)
|
||||||
|
} else {
|
||||||
|
self.navigationItem.setRightBarButton(nil, animated: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ swift_library(
|
||||||
"//submodules/Components/BalancedTextComponent",
|
"//submodules/Components/BalancedTextComponent",
|
||||||
"//submodules/TelegramUI/Components/CheckComponent",
|
"//submodules/TelegramUI/Components/CheckComponent",
|
||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
|
||||||
|
"//submodules/PromptUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ import MultilineTextComponent
|
||||||
import LocationUI
|
import LocationUI
|
||||||
import TabSelectorComponent
|
import TabSelectorComponent
|
||||||
import LanguageSelectionScreen
|
import LanguageSelectionScreen
|
||||||
|
import PromptUI
|
||||||
|
import BottomButtonPanelComponent
|
||||||
|
|
||||||
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
||||||
private let mediaBadgeTextColor = UIColor.white
|
private let mediaBadgeTextColor = UIColor.white
|
||||||
|
|
@ -1543,7 +1545,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
}
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let scope: Scope
|
public let scope: Scope
|
||||||
private let isProfileEmbedded: Bool
|
private let isProfileEmbedded: Bool
|
||||||
private let canManageStories: Bool
|
private let canManageStories: Bool
|
||||||
|
|
||||||
|
|
@ -1568,7 +1570,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
private var mapInfoNode: LocationInfoListItemNode?
|
private var mapInfoNode: LocationInfoListItemNode?
|
||||||
private var searchHeader: ComponentView<Empty>?
|
private var searchHeader: ComponentView<Empty>?
|
||||||
|
|
||||||
private var botPreviewLanguageTab: ComponentView<Empty>?
|
private var folderTab: ComponentView<Empty>?
|
||||||
private var botPreviewFooter: ComponentView<Empty>?
|
private var botPreviewFooter: ComponentView<Empty>?
|
||||||
|
|
||||||
private var barBackgroundLayer: SimpleLayer?
|
private var barBackgroundLayer: SimpleLayer?
|
||||||
|
|
@ -1583,6 +1585,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
private var didUpdateItemsOnce: Bool = false
|
private var didUpdateItemsOnce: Bool = false
|
||||||
|
|
||||||
private var selectionPanel: ComponentView<Empty>?
|
private var selectionPanel: ComponentView<Empty>?
|
||||||
|
private var actionPanel: ComponentView<Empty>?
|
||||||
|
|
||||||
private var isDeceleratingAfterTracking = false
|
private var isDeceleratingAfterTracking = false
|
||||||
|
|
||||||
|
|
@ -1627,6 +1630,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
|
|
||||||
public var isEmptyUpdated: (Bool) -> Void = { _ in }
|
public var isEmptyUpdated: (Bool) -> Void = { _ in }
|
||||||
|
|
||||||
|
public var shouldOpenItemsWhileInSelectionMode: Bool = true
|
||||||
public private(set) var isSelectionModeActive: Bool
|
public private(set) var isSelectionModeActive: Bool
|
||||||
|
|
||||||
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
|
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
|
||||||
|
|
@ -1647,6 +1651,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
public var tabBarOffset: CGFloat {
|
public var tabBarOffset: CGFloat {
|
||||||
if case .botPreview = self.scope {
|
if case .botPreview = self.scope {
|
||||||
return 0.0
|
return 0.0
|
||||||
|
} else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
|
||||||
|
return 0.0
|
||||||
} else {
|
} else {
|
||||||
return self.itemGrid.coveringInsetOffset
|
return self.itemGrid.coveringInsetOffset
|
||||||
}
|
}
|
||||||
|
|
@ -1660,6 +1666,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
private var currentBotPreviewLanguages: [StoryListContext.State.Language] = []
|
private var currentBotPreviewLanguages: [StoryListContext.State.Language] = []
|
||||||
private var removedBotPreviewLanguages = Set<String>()
|
private var removedBotPreviewLanguages = Set<String>()
|
||||||
|
|
||||||
|
private var currentStoryFolders: [StoryListContext.State.Folder] = []
|
||||||
|
private var removedStoryFolders = Set<Int64>()
|
||||||
|
|
||||||
private var numberOfItemsToRequest: Int = 50
|
private var numberOfItemsToRequest: Int = 50
|
||||||
private var isRequestingView: Bool = false
|
private var isRequestingView: Bool = false
|
||||||
private var isFirstHistoryView: Bool = true
|
private var isFirstHistoryView: Bool = true
|
||||||
|
|
@ -1674,7 +1683,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
private let maxBotPreviewCount: Int
|
private let maxBotPreviewCount: Int
|
||||||
|
|
||||||
private let defaultListSource: StoryListContext
|
private let defaultListSource: StoryListContext
|
||||||
private var cachedListSources: [String: StoryListContext] = [:]
|
private var cachedListSources: [AnyHashable: StoryListContext] = [:]
|
||||||
|
|
||||||
public var currentBotPreviewLanguage: (id: String, name: String)? {
|
public var currentBotPreviewLanguage: (id: String, name: String)? {
|
||||||
guard let listSource = self.listSource as? BotPreviewStoryListContext else {
|
guard let listSource = self.listSource as? BotPreviewStoryListContext else {
|
||||||
|
|
@ -1688,6 +1697,19 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
}
|
}
|
||||||
return (language.id, language.name)
|
return (language.id, language.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var currentStoryFolder: (id: Int64, title: String)? {
|
||||||
|
guard let listSource = self.listSource as? PeerStoryListContext else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let id = listSource.folderId else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let folder = self.currentStoryFolders.first(where: { $0.id == id }) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (folder.id, folder.title)
|
||||||
|
}
|
||||||
|
|
||||||
public var openCurrentDate: (() -> Void)?
|
public var openCurrentDate: (() -> Void)?
|
||||||
public var paneDidScroll: (() -> Void)?
|
public var paneDidScroll: (() -> Void)?
|
||||||
|
|
@ -1749,7 +1771,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
} else {
|
} else {
|
||||||
switch self.scope {
|
switch self.scope {
|
||||||
case let .peer(id, _, isArchived):
|
case let .peer(id, _, isArchived):
|
||||||
self.listSource = PeerStoryListContext(account: context.account, peerId: id, isArchived: isArchived)
|
self.listSource = PeerStoryListContext(account: context.account, peerId: id, isArchived: isArchived, folderId: nil)
|
||||||
case let .search(peerId, query):
|
case let .search(peerId, query):
|
||||||
self.listSource = SearchStoryListContext(account: context.account, source: .hashtag(peerId, query))
|
self.listSource = SearchStoryListContext(account: context.account, source: .hashtag(peerId, query))
|
||||||
case let .location(coordinates, venue):
|
case let .location(coordinates, venue):
|
||||||
|
|
@ -1798,7 +1820,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.isProfileEmbedded {
|
if self.isProfileEmbedded || !self.shouldOpenItemsWhileInSelectionMode {
|
||||||
if let selectedIds = self.itemInteraction.selectedIds {
|
if let selectedIds = self.itemInteraction.selectedIds {
|
||||||
self.itemInteraction.toggleSelection(item.story.id, !selectedIds.contains(item.story.id))
|
self.itemInteraction.toggleSelection(item.story.id, !selectedIds.contains(item.story.id))
|
||||||
return
|
return
|
||||||
|
|
@ -2275,7 +2297,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false)
|
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false)
|
||||||
|
|
||||||
if case let .peer(id, _, isArchived) = self.scope, id == context.account.peerId, !isArchived {
|
if case let .peer(id, _, isArchived) = self.scope, id == context.account.peerId, !isArchived {
|
||||||
self.preloadArchiveListContext = PeerStoryListContext(account: context.account, peerId: context.account.peerId, isArchived: true)
|
self.preloadArchiveListContext = PeerStoryListContext(account: context.account, peerId: context.account.peerId, isArchived: true, folderId: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .location(_, venue) = scope, let mapNode = self.mapNode {
|
if case let .location(_, venue) = scope, let mapNode = self.mapNode {
|
||||||
|
|
@ -2447,6 +2469,122 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
if canManage, case let .peer(peerId, _, isArchived) = self.scope {
|
if canManage, case let .peer(peerId, _, isArchived) = self.scope {
|
||||||
|
if peerId == self.context.account.peerId && self.isProfileEmbedded {
|
||||||
|
if let folder = self.currentStoryFolder {
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Remove from Album", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
|
||||||
|
guard let self else {
|
||||||
|
f(.default)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let listSource = self.listSource as? PeerStoryListContext {
|
||||||
|
listSource.removeFromFolder(id: folder.id, itemIds: [item.id])
|
||||||
|
}
|
||||||
|
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Add to Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
||||||
|
guard let self, let c else {
|
||||||
|
f(.default)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Task { @MainActor [weak self, weak c] in
|
||||||
|
guard let self, let c else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let (peerReference, folderPreviews) = await PeerStoryListContext.folderPreviews(peerId: peerId, account: self.context.account).get()
|
||||||
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, iconPosition: .left, action: { c ,f in
|
||||||
|
c?.popItems()
|
||||||
|
})))
|
||||||
|
items.append(.separator)
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "New Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] c, f in
|
||||||
|
guard let self else {
|
||||||
|
f(.default)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c?.dismiss(completion: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentAddStoryFolder(addItems: [item])
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
|
||||||
|
for folderPreview in folderPreviews {
|
||||||
|
var iconSource: ContextMenuActionItemIconSource?
|
||||||
|
if let story = folderPreview.item {
|
||||||
|
var imageSignal: Signal<UIImage?, NoError>?
|
||||||
|
|
||||||
|
var selectedMedia: Media?
|
||||||
|
if let image = story.media._asMedia() as? TelegramMediaImage {
|
||||||
|
selectedMedia = image
|
||||||
|
} else if let file = story.media._asMedia() as? TelegramMediaFile {
|
||||||
|
selectedMedia = file
|
||||||
|
}
|
||||||
|
|
||||||
|
if let selectedMedia {
|
||||||
|
if let result = self.directMediaImageCache.getImage(peer: peerReference, story: story, media: selectedMedia, width: 24, aspectRatio: 1.0, possibleWidths: [24], includeBlurred: false, synchronous: true) {
|
||||||
|
if let loadSignal = result.loadSignal {
|
||||||
|
imageSignal = .single(result.image) |> then(loadSignal)
|
||||||
|
} else {
|
||||||
|
imageSignal = .single(result.image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let imageSignal {
|
||||||
|
iconSource = ContextMenuActionItemIconSource(
|
||||||
|
size: CGSize(width: 24.0, height: 24.0),
|
||||||
|
cornerRadius: 5.0,
|
||||||
|
signal: imageSignal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon: (PresentationTheme) -> UIImage? = { _ in nil }
|
||||||
|
if iconSource == nil {
|
||||||
|
icon = { theme in
|
||||||
|
return generateImage(CGSize(width: 24.0, height: 24.0), opaque: false, scale: nil, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(theme.contextMenu.primaryColor.withMultipliedAlpha(0.1).cgColor)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 5.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: folderPreview.folder.title, icon: icon, iconSource: iconSource, iconPosition: .left, action: { [weak self] c, f in
|
||||||
|
guard let self else {
|
||||||
|
f(.default)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c?.dismiss(completion: {})
|
||||||
|
|
||||||
|
if let listSource = self.listSource as? PeerStoryListContext {
|
||||||
|
listSource.addToFolder(id: folderPreview.folder.id, items: [item])
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.pushItems(items: .single(ContextController.Items(content: .list(items))))
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
items.append(.separator)
|
||||||
|
}
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: !isArchived ? self.presentationData.strings.StoryList_ItemAction_Archive : self.presentationData.strings.StoryList_ItemAction_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
items.append(.action(ContextMenuActionItem(text: !isArchived ? self.presentationData.strings.StoryList_ItemAction_Archive : self.presentationData.strings.StoryList_ItemAction_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
@ -2784,6 +2922,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
}
|
}
|
||||||
botPreviewLanguages.sort(by: { $0.name < $1.name })
|
botPreviewLanguages.sort(by: { $0.name < $1.name })
|
||||||
|
|
||||||
|
var storyFolders = self.currentStoryFolders
|
||||||
|
for folder in state.availableFolders {
|
||||||
|
if !storyFolders.contains(where: { $0.id == folder.id }) && !self.removedStoryFolders.contains(folder.id) {
|
||||||
|
storyFolders.append(folder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var hadLocalItems = false
|
var hadLocalItems = false
|
||||||
if let currentListState = self.currentListState {
|
if let currentListState = self.currentListState {
|
||||||
for item in currentListState.items {
|
for item in currentListState.items {
|
||||||
|
|
@ -2811,8 +2956,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
|
|
||||||
self.updateItemsFromState(state: state, firstTime: firstTime, reloadAtTop: reloadAtTop, synchronous: synchronous, animated: false)
|
self.updateItemsFromState(state: state, firstTime: firstTime, reloadAtTop: reloadAtTop, synchronous: synchronous, animated: false)
|
||||||
|
|
||||||
if self.currentBotPreviewLanguages != botPreviewLanguages || reloadAtTop {
|
if self.currentBotPreviewLanguages != botPreviewLanguages || self.currentStoryFolders != storyFolders || reloadAtTop {
|
||||||
self.currentBotPreviewLanguages = botPreviewLanguages
|
self.currentBotPreviewLanguages = botPreviewLanguages
|
||||||
|
self.currentStoryFolders = storyFolders
|
||||||
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
|
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
|
||||||
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: .immediate)
|
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
@ -2870,7 +3016,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
isReorderable = !item.storyItem.isPending
|
isReorderable = !item.storyItem.isPending
|
||||||
case let .peer(id, _, _):
|
case let .peer(id, _, _):
|
||||||
if id == self.context.account.peerId {
|
if id == self.context.account.peerId {
|
||||||
isReorderable = state.pinnedIds.contains(item.storyItem.id)
|
if self.currentStoryFolder != nil {
|
||||||
|
isReorderable = true
|
||||||
|
} else {
|
||||||
|
isReorderable = state.pinnedIds.contains(item.storyItem.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case let .search(peerId, _):
|
case let .search(peerId, _):
|
||||||
if peerId != nil {
|
if peerId != nil {
|
||||||
|
|
@ -2963,11 +3113,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
if case .botPreview = self.scope {
|
if case .botPreview = self.scope {
|
||||||
} else if case let .peer(id, _, _) = self.scope {
|
} else if case let .peer(id, _, _) = self.scope {
|
||||||
if id == self.context.account.peerId {
|
if id == self.context.account.peerId {
|
||||||
let maxPinnedIndex = items.items.lastIndex(where: { ($0 as? VisualMediaItem)?.isPinned == true })
|
if self.currentStoryFolder != nil {
|
||||||
if let maxPinnedIndex {
|
|
||||||
toIndex = min(toIndex, maxPinnedIndex)
|
|
||||||
} else {
|
} else {
|
||||||
return
|
let maxPinnedIndex = items.items.lastIndex(where: { ($0 as? VisualMediaItem)?.isPinned == true })
|
||||||
|
if let maxPinnedIndex {
|
||||||
|
toIndex = min(toIndex, maxPinnedIndex)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3379,8 +3532,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
if let _ = self.mapNode {
|
if let _ = self.mapNode {
|
||||||
self.updateMapLayout(size: currentParams.size, topInset: currentParams.topInset, bottomInset: currentParams.bottomInset, deviceMetrics: currentParams.deviceMetrics, transition: transition)
|
self.updateMapLayout(size: currentParams.size, topInset: currentParams.topInset, bottomInset: currentParams.bottomInset, deviceMetrics: currentParams.deviceMetrics, transition: transition)
|
||||||
}
|
}
|
||||||
if case .botPreview = self.scope, self.canManageStories {
|
if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
|
||||||
self.updateBotPreviewLanguageTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition)
|
self.updateFolderTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition)
|
||||||
|
} else if case .botPreview = self.scope, self.canManageStories {
|
||||||
|
self.updateFolderTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition)
|
||||||
self.updateBotPreviewFooter(size: currentParams.size, bottomInset: 0.0, transition: transition)
|
self.updateBotPreviewFooter(size: currentParams.size, bottomInset: 0.0, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3536,40 +3691,69 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateBotPreviewLanguageTab(size: CGSize, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
private func updateFolderTab(size: CGSize, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
guard case .botPreview = self.scope, self.canManageStories else {
|
var displayFolderTab = false
|
||||||
|
if case .botPreview = self.scope, self.canManageStories {
|
||||||
|
displayFolderTab = true
|
||||||
|
} else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
|
||||||
|
displayFolderTab = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !displayFolderTab {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let botPreviewLanguageTab: ComponentView<Empty>
|
let folderTab: ComponentView<Empty>
|
||||||
if let current = self.botPreviewLanguageTab {
|
if let current = self.folderTab {
|
||||||
botPreviewLanguageTab = current
|
folderTab = current
|
||||||
} else {
|
} else {
|
||||||
botPreviewLanguageTab = ComponentView()
|
folderTab = ComponentView()
|
||||||
self.botPreviewLanguageTab = botPreviewLanguageTab
|
self.folderTab = folderTab
|
||||||
}
|
}
|
||||||
|
|
||||||
var languageItems: [TabSelectorComponent.Item] = []
|
var folderItems: [TabSelectorComponent.Item] = []
|
||||||
languageItems.append(TabSelectorComponent.Item(
|
let mainTitle: String
|
||||||
|
let addTitle: String
|
||||||
|
if case .botPreview = self.scope {
|
||||||
|
mainTitle = self.presentationData.strings.BotPreviews_LanguageTab_Main
|
||||||
|
addTitle = self.presentationData.strings.BotPreviews_LanguageTab_Add
|
||||||
|
} else {
|
||||||
|
//TODO:localize
|
||||||
|
mainTitle = "All Stories"
|
||||||
|
addTitle = "+ Add Album"
|
||||||
|
}
|
||||||
|
folderItems.append(TabSelectorComponent.Item(
|
||||||
id: AnyHashable("_main"),
|
id: AnyHashable("_main"),
|
||||||
title: self.presentationData.strings.BotPreviews_LanguageTab_Main
|
title: mainTitle
|
||||||
))
|
))
|
||||||
for language in self.currentBotPreviewLanguages {
|
|
||||||
languageItems.append(TabSelectorComponent.Item(
|
if case .botPreview = self.scope {
|
||||||
id: AnyHashable(language.id),
|
for language in self.currentBotPreviewLanguages {
|
||||||
title: language.name
|
folderItems.append(TabSelectorComponent.Item(
|
||||||
))
|
id: AnyHashable(language.id),
|
||||||
|
title: language.name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for folder in self.currentStoryFolders {
|
||||||
|
folderItems.append(TabSelectorComponent.Item(
|
||||||
|
id: AnyHashable(folder.id),
|
||||||
|
title: folder.title
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
languageItems.append(TabSelectorComponent.Item(
|
folderItems.append(TabSelectorComponent.Item(
|
||||||
id: AnyHashable("_add"),
|
id: AnyHashable("_add"),
|
||||||
title: self.presentationData.strings.BotPreviews_LanguageTab_Add
|
title: addTitle
|
||||||
))
|
))
|
||||||
var selectedLanguageId = "_main"
|
var selectedId = AnyHashable("_main")
|
||||||
if let listSource = self.listSource as? BotPreviewStoryListContext, let language = listSource.language {
|
if let listSource = self.listSource as? BotPreviewStoryListContext, let language = listSource.language {
|
||||||
selectedLanguageId = language
|
selectedId = AnyHashable(language)
|
||||||
|
} else if let listSource = self.listSource as? PeerStoryListContext, let folderId = listSource.folderId {
|
||||||
|
selectedId = AnyHashable(folderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
let botPreviewLanguageTabSize = botPreviewLanguageTab.update(
|
let folderTabSize = folderTab.update(
|
||||||
transition: ComponentTransition(transition),
|
transition: ComponentTransition(transition),
|
||||||
component: AnyComponent(TabSelectorComponent(
|
component: AnyComponent(TabSelectorComponent(
|
||||||
colors: TabSelectorComponent.Colors(
|
colors: TabSelectorComponent.Colors(
|
||||||
|
|
@ -3581,39 +3765,53 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
spacing: 9.0,
|
spacing: 9.0,
|
||||||
verticalInset: 11.0
|
verticalInset: 11.0
|
||||||
),
|
),
|
||||||
items: languageItems,
|
items: folderItems,
|
||||||
selectedId: AnyHashable(selectedLanguageId),
|
selectedId: selectedId,
|
||||||
setSelectedId: { [weak self] id in
|
setSelectedId: { [weak self] id in
|
||||||
guard let self, let id = id.base as? String else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if id == "_add" {
|
if let id = id.base as? String {
|
||||||
self.presentAddBotPreviewLanguage()
|
if id == "_add" {
|
||||||
} else if id == "_main" {
|
if case .botPreview = self.scope {
|
||||||
self.setBotPreviewLanguage(id: nil, assumeEmpty: false)
|
self.presentAddBotPreviewLanguage()
|
||||||
} else if let language = self.currentBotPreviewLanguages.first(where: { $0.id == id }) {
|
} else {
|
||||||
self.setBotPreviewLanguage(id: language.id, assumeEmpty: false)
|
self.presentAddStoryFolder()
|
||||||
|
}
|
||||||
|
} else if id == "_main" {
|
||||||
|
if case .botPreview = self.scope {
|
||||||
|
self.setBotPreviewLanguage(id: nil, assumeEmpty: false)
|
||||||
|
} else {
|
||||||
|
self.setStoryFolder(id: nil, assumeEmpty: false)
|
||||||
|
}
|
||||||
|
} else if let language = self.currentBotPreviewLanguages.first(where: { $0.id == id }) {
|
||||||
|
self.setBotPreviewLanguage(id: language.id, assumeEmpty: false)
|
||||||
|
}
|
||||||
|
} else if let id = id.base as? Int64 {
|
||||||
|
if let folder = self.currentStoryFolders.first(where: { $0.id == id }) {
|
||||||
|
self.setStoryFolder(id: folder.id, assumeEmpty: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width, height: 44.0)
|
containerSize: CGSize(width: size.width, height: 44.0)
|
||||||
)
|
)
|
||||||
var botPreviewLanguageTabFrame = CGRect(origin: CGPoint(x: floor((size.width - botPreviewLanguageTabSize.width) * 0.5), y: topInset - 11.0), size: botPreviewLanguageTabSize)
|
var folderTabFrame = CGRect(origin: CGPoint(x: floor((size.width - folderTabSize.width) * 0.5), y: topInset - 11.0), size: folderTabSize)
|
||||||
|
|
||||||
let effectiveScrollingOffset: CGFloat
|
let effectiveScrollingOffset: CGFloat
|
||||||
effectiveScrollingOffset = self.itemGrid.scrollingOffset
|
effectiveScrollingOffset = self.itemGrid.scrollingOffset
|
||||||
botPreviewLanguageTabFrame.origin.y -= effectiveScrollingOffset
|
folderTabFrame.origin.y -= effectiveScrollingOffset
|
||||||
|
|
||||||
let isSelectingOrReordering = self.isReordering || self.itemInteraction.selectedIds != nil
|
let isSelectingOrReordering = self.isReordering || self.itemInteraction.selectedIds != nil
|
||||||
|
|
||||||
if let botPreviewLanguageTabView = botPreviewLanguageTab.view {
|
if let folderTabView = folderTab.view {
|
||||||
if botPreviewLanguageTabView.superview == nil {
|
if folderTabView.superview == nil {
|
||||||
self.view.addSubview(botPreviewLanguageTabView)
|
self.view.addSubview(folderTabView)
|
||||||
}
|
}
|
||||||
transition.updateFrame(view: botPreviewLanguageTabView, frame: botPreviewLanguageTabFrame)
|
transition.updateFrame(view: folderTabView, frame: folderTabFrame)
|
||||||
transition.updateAlpha(layer: botPreviewLanguageTabView.layer, alpha: isSelectingOrReordering ? 0.5 : 1.0)
|
transition.updateAlpha(layer: folderTabView.layer, alpha: isSelectingOrReordering ? 0.5 : 1.0)
|
||||||
botPreviewLanguageTabView.isUserInteractionEnabled = !isSelectingOrReordering
|
folderTabView.isUserInteractionEnabled = !isSelectingOrReordering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3742,15 +3940,24 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
var listBottomInset = bottomInset
|
var listBottomInset = bottomInset
|
||||||
var bottomInset = bottomInset
|
var bottomInset = bottomInset
|
||||||
|
|
||||||
|
var displayFolderTab = false
|
||||||
if case .botPreview = self.scope, self.canManageStories {
|
if case .botPreview = self.scope, self.canManageStories {
|
||||||
updateBotPreviewLanguageTab(size: size, topInset: topInset, transition: transition)
|
displayFolderTab = true
|
||||||
|
} else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
|
||||||
|
displayFolderTab = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if displayFolderTab {
|
||||||
|
updateFolderTab(size: size, topInset: topInset, transition: transition)
|
||||||
gridTopInset += 50.0
|
gridTopInset += 50.0
|
||||||
|
|
||||||
updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition)
|
if case .botPreview = self.scope {
|
||||||
if let botPreviewFooterView = self.botPreviewFooter?.view {
|
updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition)
|
||||||
|
if let botPreviewFooterView = self.botPreviewFooter?.view {
|
||||||
listBottomInset += 18.0 + botPreviewFooterView.bounds.height
|
listBottomInset += 18.0 + botPreviewFooterView.bounds.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, case let .peer(peerId, _, isArchived) = self.scope {
|
if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, case let .peer(peerId, _, isArchived) = self.scope {
|
||||||
let selectionPanel: ComponentView<Empty>
|
let selectionPanel: ComponentView<Empty>
|
||||||
|
|
@ -3930,13 +4137,200 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
} else if let selectionPanel = self.selectionPanel {
|
} else if let selectionPanel = self.selectionPanel {
|
||||||
self.selectionPanel = nil
|
self.selectionPanel = nil
|
||||||
if let selectionPanelView = selectionPanel.view {
|
if let selectionPanelView = selectionPanel.view {
|
||||||
transition.updateFrame(view: selectionPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: selectionPanelView.bounds.size))
|
transition.updateFrame(view: selectionPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: selectionPanelView.bounds.size), completion: { [weak selectionPanelView] _ in
|
||||||
|
selectionPanelView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.selectionPanel == nil, self.isProfileEmbedded, self.canManageStories, case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded, self.currentStoryFolder != nil, let items = self.items, !items.items.isEmpty {
|
||||||
|
let actionPanel: ComponentView<Empty>
|
||||||
|
var actionPanelTransition = ComponentTransition(transition)
|
||||||
|
if let current = self.actionPanel {
|
||||||
|
actionPanel = current
|
||||||
|
} else {
|
||||||
|
actionPanelTransition = actionPanelTransition.withAnimation(.none)
|
||||||
|
actionPanel = ComponentView()
|
||||||
|
self.actionPanel = actionPanel
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let actionPanelSize = actionPanel.update(
|
||||||
|
transition: actionPanelTransition,
|
||||||
|
component: AnyComponent(BottomButtonPanelComponent(
|
||||||
|
theme: presentationData.theme,
|
||||||
|
title: "Add Stories",
|
||||||
|
label: nil,
|
||||||
|
isEnabled: true,
|
||||||
|
insets: UIEdgeInsets(top: 0.0, left: sideInset + 12.0, bottom: bottomInset, right: sideInset + 12.0),
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentAddStoriesToFolder()
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: size
|
||||||
|
)
|
||||||
|
let actionPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - actionPanelSize.height), size: actionPanelSize)
|
||||||
|
if let actionPanelView = actionPanel.view {
|
||||||
|
if actionPanelView.superview == nil {
|
||||||
|
self.view.addSubview(actionPanelView)
|
||||||
|
transition.animatePositionAdditive(layer: actionPanelView.layer, offset: CGPoint(x: 0.0, y: actionPanelFrame.height))
|
||||||
|
}
|
||||||
|
actionPanelTransition.setFrame(view: actionPanelView, frame: actionPanelFrame)
|
||||||
|
}
|
||||||
|
bottomInset = actionPanelSize.height
|
||||||
|
listBottomInset += actionPanelSize.height
|
||||||
|
} else if let actionPanel = self.actionPanel {
|
||||||
|
self.actionPanel = nil
|
||||||
|
if let actionPanelView = actionPanel.view {
|
||||||
|
transition.updateFrame(view: actionPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: actionPanelView.bounds.size), completion: { [weak actionPanelView] _ in
|
||||||
|
actionPanelView?.removeFromSuperview()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
|
||||||
if case let .peer(_, _, isArchived) = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 {
|
if case let .peer(peerId, _, isArchived) = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 {
|
||||||
|
if peerId == self.context.account.peerId, self.isProfileEmbedded, self.currentStoryFolder != nil {
|
||||||
|
let emptyStateView: ComponentView<Empty>
|
||||||
|
var emptyStateTransition = ComponentTransition(transition)
|
||||||
|
if let current = self.emptyStateView {
|
||||||
|
emptyStateView = current
|
||||||
|
} else {
|
||||||
|
emptyStateTransition = .immediate
|
||||||
|
emptyStateView = ComponentView()
|
||||||
|
self.emptyStateView = emptyStateView
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let emptyStateSize = emptyStateView.update(
|
||||||
|
transition: emptyStateTransition,
|
||||||
|
component: AnyComponent(EmptyStateIndicatorComponent(
|
||||||
|
context: self.context,
|
||||||
|
theme: presentationData.theme,
|
||||||
|
fitToHeight: self.isProfileEmbedded,
|
||||||
|
animationName: nil,
|
||||||
|
title: "Organize Your Stories",
|
||||||
|
text: "Add some stories to this album.",
|
||||||
|
actionTitle: "Add to Album",
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentAddStoriesToFolder()
|
||||||
|
},
|
||||||
|
additionalActionTitle: nil,
|
||||||
|
additionalAction: {},
|
||||||
|
additionalActionSeparator: nil
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset)
|
||||||
|
)
|
||||||
|
|
||||||
|
let emptyStateFrame: CGRect
|
||||||
|
if self.isProfileEmbedded {
|
||||||
|
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset + 22.0, floor((visibleHeight - gridTopInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize)
|
||||||
|
} else {
|
||||||
|
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: gridTopInset), size: emptyStateSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let emptyStateComponentView = emptyStateView.view {
|
||||||
|
if emptyStateComponentView.superview == nil {
|
||||||
|
self.view.addSubview(emptyStateComponentView)
|
||||||
|
if self.didUpdateItemsOnce {
|
||||||
|
emptyStateComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emptyStateTransition.setFrame(view: emptyStateComponentView, frame: emptyStateFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundColor: UIColor
|
||||||
|
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||||
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
|
} else if self.isProfileEmbedded {
|
||||||
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
|
} else {
|
||||||
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.didUpdateItemsOnce {
|
||||||
|
ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor)
|
||||||
|
} else {
|
||||||
|
self.view.backgroundColor = backgroundColor
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let emptyStateView: ComponentView<Empty>
|
||||||
|
var emptyStateTransition = ComponentTransition(transition)
|
||||||
|
if let current = self.emptyStateView {
|
||||||
|
emptyStateView = current
|
||||||
|
} else {
|
||||||
|
emptyStateTransition = .immediate
|
||||||
|
emptyStateView = ComponentView()
|
||||||
|
self.emptyStateView = emptyStateView
|
||||||
|
}
|
||||||
|
let emptyStateSize = emptyStateView.update(
|
||||||
|
transition: emptyStateTransition,
|
||||||
|
component: AnyComponent(EmptyStateIndicatorComponent(
|
||||||
|
context: self.context,
|
||||||
|
theme: presentationData.theme,
|
||||||
|
fitToHeight: self.isProfileEmbedded,
|
||||||
|
animationName: "StoryListEmpty",
|
||||||
|
title: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
|
||||||
|
text: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
|
||||||
|
actionTitle: isArchived ? nil : presentationData.strings.StoryList_SavedAddAction,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.emptyAction?()
|
||||||
|
},
|
||||||
|
additionalActionTitle: (isArchived || self.isProfileEmbedded) ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
||||||
|
additionalAction: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.additionalEmptyAction?()
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset)
|
||||||
|
)
|
||||||
|
|
||||||
|
let emptyStateFrame: CGRect
|
||||||
|
if self.isProfileEmbedded {
|
||||||
|
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset, floor((visibleHeight - gridTopInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize)
|
||||||
|
} else {
|
||||||
|
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: gridTopInset), size: emptyStateSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let emptyStateComponentView = emptyStateView.view {
|
||||||
|
if emptyStateComponentView.superview == nil {
|
||||||
|
self.view.addSubview(emptyStateComponentView)
|
||||||
|
if self.didUpdateItemsOnce {
|
||||||
|
emptyStateComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emptyStateTransition.setFrame(view: emptyStateComponentView, frame: emptyStateFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundColor: UIColor
|
||||||
|
if self.isProfileEmbedded {
|
||||||
|
backgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
|
} else {
|
||||||
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.didUpdateItemsOnce {
|
||||||
|
ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor)
|
||||||
|
} else {
|
||||||
|
self.view.backgroundColor = backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if case .botPreview = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 {
|
||||||
let emptyStateView: ComponentView<Empty>
|
let emptyStateView: ComponentView<Empty>
|
||||||
var emptyStateTransition = ComponentTransition(transition)
|
var emptyStateTransition = ComponentTransition(transition)
|
||||||
if let current = self.emptyStateView {
|
if let current = self.emptyStateView {
|
||||||
|
|
@ -3946,29 +4340,44 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
emptyStateView = ComponentView()
|
emptyStateView = ComponentView()
|
||||||
self.emptyStateView = emptyStateView
|
self.emptyStateView = emptyStateView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isMainLanguage = true
|
||||||
|
if let listSource = self.listSource as? BotPreviewStoryListContext, let _ = listSource.language {
|
||||||
|
isMainLanguage = false
|
||||||
|
}
|
||||||
|
|
||||||
let emptyStateSize = emptyStateView.update(
|
let emptyStateSize = emptyStateView.update(
|
||||||
transition: emptyStateTransition,
|
transition: emptyStateTransition,
|
||||||
component: AnyComponent(EmptyStateIndicatorComponent(
|
component: AnyComponent(EmptyStateIndicatorComponent(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
theme: presentationData.theme,
|
theme: presentationData.theme,
|
||||||
fitToHeight: self.isProfileEmbedded,
|
fitToHeight: self.isProfileEmbedded,
|
||||||
animationName: "StoryListEmpty",
|
animationName: nil,
|
||||||
title: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
|
title: presentationData.strings.BotPreviews_Empty_Title,
|
||||||
text: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
|
text: presentationData.strings.BotPreviews_Empty_Text(Int32(self.maxBotPreviewCount)),
|
||||||
actionTitle: isArchived ? nil : presentationData.strings.StoryList_SavedAddAction,
|
actionTitle: self.canManageStories ? presentationData.strings.BotPreviews_Empty_Add : nil,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.emptyAction?()
|
if self.canAddMoreBotPreviews() {
|
||||||
|
self.emptyAction?()
|
||||||
|
} else {
|
||||||
|
self.presentUnableToAddMorePreviewsAlert()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
additionalActionTitle: (isArchived || self.isProfileEmbedded) ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
additionalActionTitle: self.canManageStories ? (isMainLanguage ? presentationData.strings.BotPreviews_Empty_AddTranslation : presentationData.strings.BotPreviews_Empty_DeleteTranslation) : nil,
|
||||||
additionalAction: { [weak self] in
|
additionalAction: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.additionalEmptyAction?()
|
if isMainLanguage {
|
||||||
}
|
self.presentAddBotPreviewLanguage()
|
||||||
|
} else {
|
||||||
|
self.presentDeleteBotPreviewLanguage()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
additionalActionSeparator: self.canManageStories ? presentationData.strings.BotPreviews_Empty_Separator : nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset)
|
containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset)
|
||||||
|
|
@ -3976,7 +4385,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
|
|
||||||
let emptyStateFrame: CGRect
|
let emptyStateFrame: CGRect
|
||||||
if self.isProfileEmbedded {
|
if self.isProfileEmbedded {
|
||||||
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset, floor((visibleHeight - gridTopInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize)
|
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset + 22.0, floor((visibleHeight - gridTopInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize)
|
||||||
} else {
|
} else {
|
||||||
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: gridTopInset), size: emptyStateSize)
|
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: gridTopInset), size: emptyStateSize)
|
||||||
}
|
}
|
||||||
|
|
@ -3992,8 +4401,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
}
|
}
|
||||||
|
|
||||||
let backgroundColor: UIColor
|
let backgroundColor: UIColor
|
||||||
if self.isProfileEmbedded {
|
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||||
backgroundColor = presentationData.theme.list.plainBackgroundColor
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
|
} else if self.isProfileEmbedded {
|
||||||
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
} else {
|
} else {
|
||||||
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
@ -4003,7 +4414,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
} else {
|
} else {
|
||||||
self.view.backgroundColor = backgroundColor
|
self.view.backgroundColor = backgroundColor
|
||||||
}
|
}
|
||||||
} else if case .botPreview = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 {
|
} else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded, let items = self.items, items.items.isEmpty, items.count == 0 {
|
||||||
let emptyStateView: ComponentView<Empty>
|
let emptyStateView: ComponentView<Empty>
|
||||||
var emptyStateTransition = ComponentTransition(transition)
|
var emptyStateTransition = ComponentTransition(transition)
|
||||||
if let current = self.emptyStateView {
|
if let current = self.emptyStateView {
|
||||||
|
|
@ -4073,6 +4484,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
let backgroundColor: UIColor
|
let backgroundColor: UIColor
|
||||||
if self.isProfileEmbedded, case .botPreview = self.scope {
|
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||||
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
|
} else if self.isProfileEmbedded, case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, self.isProfileEmbedded, !isArchived {
|
||||||
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
} else if self.isProfileEmbedded {
|
} else if self.isProfileEmbedded {
|
||||||
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -4101,6 +4514,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
|
|
||||||
if self.isProfileEmbedded, case .botPreview = self.scope {
|
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||||
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor)
|
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor)
|
||||||
|
} else if self.isProfileEmbedded, case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
|
||||||
|
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor)
|
||||||
} else if self.isProfileEmbedded {
|
} else if self.isProfileEmbedded {
|
||||||
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.plainBackgroundColor)
|
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.plainBackgroundColor)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -4109,6 +4524,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
} else {
|
} else {
|
||||||
if self.isProfileEmbedded, case .botPreview = self.scope {
|
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||||
self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
|
} else if self.isProfileEmbedded, case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, self.isProfileEmbedded, !isArchived {
|
||||||
|
self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
} else {
|
} else {
|
||||||
if case let .search(peerId, _) = self.scope, peerId != nil {
|
if case let .search(peerId, _) = self.scope, peerId != nil {
|
||||||
|
|
||||||
|
|
@ -4132,6 +4549,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
var adjustForSmallCount = true
|
var adjustForSmallCount = true
|
||||||
if case .botPreview = self.scope {
|
if case .botPreview = self.scope {
|
||||||
adjustForSmallCount = false
|
adjustForSmallCount = false
|
||||||
|
} else if self.currentStoryFolder != nil {
|
||||||
|
adjustForSmallCount = false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.itemGrid.pinchEnabled = items.count > 2 && !self.isReordering
|
self.itemGrid.pinchEnabled = items.count > 2 && !self.isReordering
|
||||||
|
|
@ -4234,10 +4653,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
}
|
}
|
||||||
|
|
||||||
public func canReorder() -> Bool {
|
public func canReorder() -> Bool {
|
||||||
guard let items = self.items else {
|
if case .botPreview = self.scope {
|
||||||
return false
|
guard let items = self.items else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return items.count > 1
|
||||||
|
} else {
|
||||||
|
if self.currentStoryFolder == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let items = self.items else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return items.count > 1
|
||||||
}
|
}
|
||||||
return items.count > 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentAddBotPreviewLanguage() {
|
private func presentAddBotPreviewLanguage() {
|
||||||
|
|
@ -4250,6 +4680,78 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentAddStoryFolder(addItems: [EngineStoryItem] = []) {
|
||||||
|
//TODO:localize
|
||||||
|
let promptController = promptController(
|
||||||
|
sharedContext: self.context.sharedContext,
|
||||||
|
updatedPresentationData: nil,
|
||||||
|
text: "Create a New Album",
|
||||||
|
titleFont: .bold,
|
||||||
|
subtitle: "Choose a name for your album and start adding your stories there.",
|
||||||
|
value: "",
|
||||||
|
placeholder: "Title",
|
||||||
|
characterLimit: 20,
|
||||||
|
apply: { [weak self] value in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let value {
|
||||||
|
if let listSource = self.listSource as? PeerStoryListContext {
|
||||||
|
listSource.addFolder(title: value, completion: { [weak self] id in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let self, let listSource = self.listSource as? PeerStoryListContext else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !addItems.isEmpty {
|
||||||
|
listSource.addToFolder(id: id, items: addItems)
|
||||||
|
}
|
||||||
|
self.setStoryFolder(id: id, assumeEmpty: addItems.isEmpty)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.parentController?.present(promptController, in: .window(.root))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentAddStoriesToFolder() {
|
||||||
|
guard case let .peer(peerId, _, _) = self.scope else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let folder = self.currentStoryFolder else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = self.context.sharedContext.makeStorySelectionController(context: self.context, peerId: peerId, completion: { [weak self] items in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let listSource = self.listSource as? PeerStoryListContext {
|
||||||
|
listSource.addToFolder(id: folder.id, items: items)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
controller.navigationPresentation = .modal
|
||||||
|
|
||||||
|
self.parentController?.push(controller)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func presentDeleteCurrentStoryFolder() {
|
||||||
|
if let folder = self.currentStoryFolder {
|
||||||
|
self.setStoryFolder(id: nil, assumeEmpty: false)
|
||||||
|
self.currentStoryFolders.removeAll(where: { $0.id == folder.id })
|
||||||
|
self.removedStoryFolders.insert(folder.id)
|
||||||
|
|
||||||
|
if let listContext = self.listSource as? PeerStoryListContext {
|
||||||
|
listContext.removeFolder(id: folder.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
|
||||||
|
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func presentUnableToAddMorePreviewsAlert() {
|
public func presentUnableToAddMorePreviewsAlert() {
|
||||||
self.parentController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.BotPreviews_AlertTooManyPreviews(Int32(self.maxBotPreviewCount)), actions: [
|
self.parentController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.BotPreviews_AlertTooManyPreviews(Int32(self.maxBotPreviewCount)), actions: [
|
||||||
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
||||||
|
|
@ -4335,12 +4837,32 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
}
|
}
|
||||||
|
|
||||||
if let id {
|
if let id {
|
||||||
if let cachedListSource = self.cachedListSources[id] {
|
if let cachedListSource = self.cachedListSources[AnyHashable(id)] {
|
||||||
self.listSource = cachedListSource
|
self.listSource = cachedListSource
|
||||||
} else {
|
} else {
|
||||||
let listSource = BotPreviewStoryListContext(account: self.context.account, engine: self.context.engine, peerId: peerId, language: id, assumeEmpty: assumeEmpty)
|
let listSource = BotPreviewStoryListContext(account: self.context.account, engine: self.context.engine, peerId: peerId, language: id, assumeEmpty: assumeEmpty)
|
||||||
self.listSource = listSource
|
self.listSource = listSource
|
||||||
self.cachedListSources[id] = listSource
|
self.cachedListSources[AnyHashable(id)] = listSource
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.listSource = self.defaultListSource
|
||||||
|
}
|
||||||
|
|
||||||
|
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setStoryFolder(id: Int64?, assumeEmpty: Bool) {
|
||||||
|
if let listSource = self.listSource as? PeerStoryListContext, listSource.folderId == id {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let id {
|
||||||
|
if let cachedListSource = self.cachedListSources[AnyHashable(id)] {
|
||||||
|
self.listSource = cachedListSource
|
||||||
|
} else {
|
||||||
|
let listSource = PeerStoryListContext(account: self.context.account, peerId: self.context.account.peerId, isArchived: false, folderId: id)
|
||||||
|
self.listSource = listSource
|
||||||
|
//self.cachedListSources[AnyHashable(id)] = listSource
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.listSource = self.defaultListSource
|
self.listSource = self.defaultListSource
|
||||||
|
|
@ -4388,20 +4910,26 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||||
listSource.reorderItems(media: reorderedMedia)
|
listSource.reorderItems(media: reorderedMedia)
|
||||||
}
|
}
|
||||||
} else if case let .peer(id, _, _) = self.scope, id == self.context.account.peerId, let items = self.items {
|
} else if case let .peer(id, _, _) = self.scope, id == self.context.account.peerId, let items = self.items {
|
||||||
var updatedPinnedIds: [Int32] = []
|
if let _ = self.currentStoryFolder {
|
||||||
for id in reorderedIds {
|
if let listSource = self.listSource as? PeerStoryListContext {
|
||||||
inner: for item in items.items {
|
listSource.reorderItemsInFolder(itemIds: reorderedIds.map { $0.id })
|
||||||
if let item = item as? VisualMediaItem {
|
}
|
||||||
if item.storyId == id {
|
} else {
|
||||||
if item.isPinned {
|
var updatedPinnedIds: [Int32] = []
|
||||||
updatedPinnedIds.append(id.id)
|
for id in reorderedIds {
|
||||||
break inner
|
inner: for item in items.items {
|
||||||
|
if let item = item as? VisualMediaItem {
|
||||||
|
if item.storyId == id {
|
||||||
|
if item.isPinned {
|
||||||
|
updatedPinnedIds.append(id.id)
|
||||||
|
break inner
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: id, ids: updatedPinnedIds).startStandalone()
|
||||||
}
|
}
|
||||||
let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: id, ids: updatedPinnedIds).startStandalone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,8 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/InteractiveTextComponent",
|
"//submodules/TelegramUI/Components/InteractiveTextComponent",
|
||||||
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
|
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
|
||||||
|
"//submodules/DirectMediaImageCache",
|
||||||
|
"//submodules/PromptUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -1456,6 +1456,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
let listContext: StoryListContext
|
||||||
|
|
||||||
public private(set) var stateValue: StoryContentContextState?
|
public private(set) var stateValue: StoryContentContextState?
|
||||||
public var state: Signal<StoryContentContextState, NoError> {
|
public var state: Signal<StoryContentContextState, NoError> {
|
||||||
|
|
@ -1486,6 +1487,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
||||||
|
|
||||||
public init(context: AccountContext, listContext: StoryListContext, initialId: StoryId?, splitIndexIntoDays: Bool) {
|
public init(context: AccountContext, listContext: StoryListContext, initialId: StoryId?, splitIndexIntoDays: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.listContext = listContext
|
||||||
|
|
||||||
let preferHighQualityStories: Signal<Bool, NoError> = combineLatest(
|
let preferHighQualityStories: Signal<Bool, NoError> = combineLatest(
|
||||||
context.sharedContext.automaticMediaDownloadSettings
|
context.sharedContext.automaticMediaDownloadSettings
|
||||||
|
|
|
||||||
|
|
@ -1688,6 +1688,39 @@ private final class StoryContainerScreenComponent: Component {
|
||||||
performReorderAction?()
|
performReorderAction?()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
createToFolder: { [weak self] title, items in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let content = component.content as? PeerStoryListContentContextImpl {
|
||||||
|
if let listSource = content.listContext as? PeerStoryListContext {
|
||||||
|
listSource.addFolder(title: title, completion: { [weak listSource] id in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let listSource else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !items.isEmpty {
|
||||||
|
listSource.addToFolder(id: id, items: items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addToFolder: { [weak self] folderId in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let stateValue = self.stateValue, let slice = stateValue.slice else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let content = component.content as? PeerStoryListContentContextImpl {
|
||||||
|
if let listSource = content.listContext as? PeerStoryListContext {
|
||||||
|
listSource.addToFolder(id: folderId, items: [slice.item.storyItem])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
controller: { [weak self] in
|
controller: { [weak self] in
|
||||||
return self?.environment?.controller()
|
return self?.environment?.controller()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ import StoryFooterPanelComponent
|
||||||
import TelegramNotices
|
import TelegramNotices
|
||||||
import SliderContextItem
|
import SliderContextItem
|
||||||
import SaveProgressScreen
|
import SaveProgressScreen
|
||||||
|
import DirectMediaImageCache
|
||||||
|
import PromptUI
|
||||||
|
|
||||||
public final class StoryAvailableReactions: Equatable {
|
public final class StoryAvailableReactions: Equatable {
|
||||||
let reactionItems: [ReactionItem]
|
let reactionItems: [ReactionItem]
|
||||||
|
|
@ -120,6 +122,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||||
public let delete: () -> Void
|
public let delete: () -> Void
|
||||||
public let markAsSeen: (StoryId) -> Void
|
public let markAsSeen: (StoryId) -> Void
|
||||||
public let reorder: () -> Void
|
public let reorder: () -> Void
|
||||||
|
public let createToFolder: (String, [EngineStoryItem]) -> Void
|
||||||
|
public let addToFolder: (Int64) -> Void
|
||||||
public let controller: () -> ViewController?
|
public let controller: () -> ViewController?
|
||||||
public let toggleAmbientMode: () -> Void
|
public let toggleAmbientMode: () -> Void
|
||||||
public let keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>
|
public let keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>
|
||||||
|
|
@ -157,6 +161,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||||
delete: @escaping () -> Void,
|
delete: @escaping () -> Void,
|
||||||
markAsSeen: @escaping (StoryId) -> Void,
|
markAsSeen: @escaping (StoryId) -> Void,
|
||||||
reorder: @escaping () -> Void,
|
reorder: @escaping () -> Void,
|
||||||
|
createToFolder: @escaping (String, [EngineStoryItem]) -> Void,
|
||||||
|
addToFolder: @escaping (Int64) -> Void,
|
||||||
controller: @escaping () -> ViewController?,
|
controller: @escaping () -> ViewController?,
|
||||||
toggleAmbientMode: @escaping () -> Void,
|
toggleAmbientMode: @escaping () -> Void,
|
||||||
keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>,
|
keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>,
|
||||||
|
|
@ -193,6 +199,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||||
self.delete = delete
|
self.delete = delete
|
||||||
self.markAsSeen = markAsSeen
|
self.markAsSeen = markAsSeen
|
||||||
self.reorder = reorder
|
self.reorder = reorder
|
||||||
|
self.createToFolder = createToFolder
|
||||||
|
self.addToFolder = addToFolder
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.toggleAmbientMode = toggleAmbientMode
|
self.toggleAmbientMode = toggleAmbientMode
|
||||||
self.keyboardInputData = keyboardInputData
|
self.keyboardInputData = keyboardInputData
|
||||||
|
|
@ -6101,6 +6109,102 @@ public final class StoryItemSetContainerComponent: Component {
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Add to Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
||||||
|
guard let self, let c else {
|
||||||
|
f(.default)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Task { @MainActor [weak self, weak c] in
|
||||||
|
guard let self, let component = self.component, let peerId = component.slice.item.peerId, let c else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let (peerReference, folderPreviews) = await PeerStoryListContext.folderPreviews(peerId: peerId, account: component.context.account).get()
|
||||||
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, iconPosition: .left, action: { c ,f in
|
||||||
|
c?.popItems()
|
||||||
|
})))
|
||||||
|
items.append(.separator)
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "New Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] c, f in
|
||||||
|
guard let self else {
|
||||||
|
f(.default)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c?.dismiss(completion: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentAddStoryFolder(addItems: [component.slice.item.storyItem])
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
|
||||||
|
for folderPreview in folderPreviews {
|
||||||
|
var iconSource: ContextMenuActionItemIconSource?
|
||||||
|
if let story = folderPreview.item {
|
||||||
|
var imageSignal: Signal<UIImage?, NoError>?
|
||||||
|
|
||||||
|
var selectedMedia: Media?
|
||||||
|
if let image = story.media._asMedia() as? TelegramMediaImage {
|
||||||
|
selectedMedia = image
|
||||||
|
} else if let file = story.media._asMedia() as? TelegramMediaFile {
|
||||||
|
selectedMedia = file
|
||||||
|
}
|
||||||
|
|
||||||
|
if let selectedMedia {
|
||||||
|
let directMediaImageCache = DirectMediaImageCache(account: component.context.account)
|
||||||
|
if let result = directMediaImageCache.getImage(peer: peerReference, story: story, media: selectedMedia, width: 24, aspectRatio: 1.0, possibleWidths: [24], includeBlurred: false, synchronous: true) {
|
||||||
|
if let loadSignal = result.loadSignal {
|
||||||
|
imageSignal = .single(result.image) |> then(loadSignal)
|
||||||
|
} else {
|
||||||
|
imageSignal = .single(result.image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let imageSignal {
|
||||||
|
iconSource = ContextMenuActionItemIconSource(
|
||||||
|
size: CGSize(width: 24.0, height: 24.0),
|
||||||
|
cornerRadius: 5.0,
|
||||||
|
signal: imageSignal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon: (PresentationTheme) -> UIImage? = { _ in nil }
|
||||||
|
if iconSource == nil {
|
||||||
|
icon = { theme in
|
||||||
|
return generateImage(CGSize(width: 24.0, height: 24.0), opaque: false, scale: nil, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(theme.contextMenu.primaryColor.withMultipliedAlpha(0.1).cgColor)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 5.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: folderPreview.folder.title, icon: icon, iconSource: iconSource, iconPosition: .left, action: { [weak self] c, f in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
f(.default)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c?.dismiss(completion: {})
|
||||||
|
|
||||||
|
component.addToFolder(folderPreview.folder.id)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.pushItems(items: .single(ContextController.Items(content: .list(items))))
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
if case .file = component.slice.item.storyItem.media {
|
if case .file = component.slice.item.storyItem.media {
|
||||||
var speedValue: String = presentationData.strings.PlaybackSpeed_Normal
|
var speedValue: String = presentationData.strings.PlaybackSpeed_Normal
|
||||||
var speedIconText: String = "1x"
|
var speedIconText: String = "1x"
|
||||||
|
|
@ -7028,6 +7132,34 @@ public final class StoryItemSetContainerComponent: Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentAddStoryFolder(addItems: [EngineStoryItem] = []) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
|
|
||||||
|
let promptController = promptController(
|
||||||
|
sharedContext: component.context.sharedContext,
|
||||||
|
updatedPresentationData: (initial: presentationData, signal: .single(presentationData)),
|
||||||
|
text: "Create a New Album",
|
||||||
|
titleFont: .bold,
|
||||||
|
subtitle: "Choose a name for your album and start adding your stories there.",
|
||||||
|
value: "",
|
||||||
|
placeholder: "Title",
|
||||||
|
characterLimit: 20,
|
||||||
|
apply: { [weak self] value in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let value {
|
||||||
|
component.createToFolder(value, addItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
component.presentController(promptController, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func displayMutedVideoTooltip() {
|
func displayMutedVideoTooltip() {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -2546,6 +2546,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||||
return PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: isArchive ? .archive : .saved)
|
return PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: isArchive ? .archive : .saved)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func makeStorySelectionController(context: AccountContext, peerId: EnginePeer.Id, completion: @escaping ([EngineStoryItem]) -> Void) -> ViewController {
|
||||||
|
return PeerInfoStoryGridScreen(context: context, peerId: peerId, scope: .saved, selectionModeCompletion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
public func makeArchiveSettingsController(context: AccountContext) -> ViewController {
|
public func makeArchiveSettingsController(context: AccountContext) -> ViewController {
|
||||||
return archiveSettingsController(context: context)
|
return archiveSettingsController(context: context)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ swift_library(
|
||||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
"//submodules/ShimmerEffect",
|
"//submodules/ShimmerEffect",
|
||||||
|
"//submodules/UIKitRuntimeUtils",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import BalancedTextComponent
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import MultilineTextWithEntitiesComponent
|
import MultilineTextWithEntitiesComponent
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
import UIKitRuntimeUtils
|
||||||
|
|
||||||
public enum TooltipActiveTextItem {
|
public enum TooltipActiveTextItem {
|
||||||
case url(String, Bool)
|
case url(String, Bool)
|
||||||
|
|
@ -1328,6 +1329,10 @@ public final class TooltipScreen: ViewController {
|
||||||
override public func viewDidAppear(_ animated: Bool) {
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
if self.ignoreAppearanceMethodInvocations() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.controllerNode.animateIn()
|
self.controllerNode.animateIn()
|
||||||
self.resetDismissTimeout(duration: self.displayDuration)
|
self.resetDismissTimeout(duration: self.displayDuration)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
third-party/XcodeGen
vendored
Submodule
1
third-party/XcodeGen
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 53cb43cb66908a28812d7629d03fed94c9827a24
|
||||||
Loading…
Add table
Add a link
Reference in a new issue