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"]
|
||||
path = third-party/td/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 makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> 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 makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||
func makeBusinessSetupScreen(context: AccountContext) -> ViewController
|
||||
|
|
|
|||
|
|
@ -165,8 +165,10 @@ private final class PromptAlertContentNode: AlertContentNode {
|
|||
private let strings: PresentationStrings
|
||||
private let text: String
|
||||
private let titleFont: PromptControllerTitleFont
|
||||
private let subtitle: String?
|
||||
|
||||
private let textNode: ASTextNode
|
||||
private let subtitleNode: ASTextNode?
|
||||
let inputFieldNode: PromptInputFieldNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
|
|
@ -189,14 +191,23 @@ private final class PromptAlertContentNode: AlertContentNode {
|
|||
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.text = text
|
||||
self.titleFont = titleFont
|
||||
self.subtitle = subtitle
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
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.text = value ?? ""
|
||||
|
||||
|
|
@ -220,6 +231,9 @@ private final class PromptAlertContentNode: AlertContentNode {
|
|||
super.init()
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
if let subtitleNode = self.subtitleNode {
|
||||
self.addSubnode(subtitleNode)
|
||||
}
|
||||
|
||||
self.addSubnode(self.inputFieldNode)
|
||||
|
||||
|
|
@ -268,6 +282,10 @@ private final class PromptAlertContentNode: AlertContentNode {
|
|||
titleFontValue = Font.semibold(17.0)
|
||||
}
|
||||
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
|
||||
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))
|
||||
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
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
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)
|
||||
|
||||
var contentWidth = max(titleSize.width, minActionsWidth)
|
||||
if let subtitleSize {
|
||||
contentWidth = max(contentWidth, subtitleSize.width)
|
||||
}
|
||||
contentWidth = max(contentWidth, 234.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.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)))
|
||||
|
||||
|
|
@ -407,7 +439,7 @@ public enum PromptControllerTitleFont {
|
|||
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 }
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
|
|
@ -421,7 +453,7 @@ public func promptController(sharedContext: SharedAccountContext, updatedPresent
|
|||
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 = {
|
||||
dismissImpl?(true)
|
||||
applyImpl?()
|
||||
|
|
|
|||
|
|
@ -2127,7 +2127,7 @@ public func channelStatsController(
|
|||
}
|
||||
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()
|
||||
storiesPromise.set(
|
||||
.single(nil)
|
||||
|
|
|
|||
|
|
@ -943,7 +943,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[2109703795] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
||||
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($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[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
|
||||
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
|
||||
|
|
|
|||
|
|
@ -636,14 +636,14 @@ public extension Api {
|
|||
}
|
||||
public extension Api {
|
||||
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?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
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 {
|
||||
buffer.appendInt32(2139438098)
|
||||
buffer.appendInt32(12386139)
|
||||
}
|
||||
serializeInt32(flags, 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 << 5) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||
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
|
||||
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 {
|
||||
|
|
@ -688,8 +690,8 @@ public extension Api {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
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):
|
||||
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)])
|
||||
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), ("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):
|
||||
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() {
|
||||
_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 _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
|
|
@ -742,8 +748,10 @@ public extension Api {
|
|||
let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil
|
||||
let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != 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 {
|
||||
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 _c15 = (Int(_1!) & Int(1 << 8) == 0) || _15 != nil
|
||||
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 {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
|||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 207
|
||||
return 210
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
|
|
|||
|
|
@ -531,11 +531,13 @@ private final class CachedPeerStoryListHead: Codable {
|
|||
let items: [Stories.StoredItem]
|
||||
let pinnedIds: [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.pinnedIds = pinnedIds
|
||||
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 items: [Item]
|
||||
public var availableLanguages: [Language]
|
||||
public var availableFolders: [Folder]
|
||||
public var pinnedIds: [Int32]
|
||||
public var totalCount: Int
|
||||
public var loadMoreToken: AnyHashable?
|
||||
|
|
@ -592,6 +605,7 @@ public struct StoryListContextState: Equatable {
|
|||
peerReference: PeerReference?,
|
||||
items: [Item],
|
||||
availableLanguages: [Language],
|
||||
availableFolders: [Folder],
|
||||
pinnedIds: [Int32],
|
||||
totalCount: Int,
|
||||
loadMoreToken: AnyHashable?,
|
||||
|
|
@ -603,6 +617,7 @@ public struct StoryListContextState: Equatable {
|
|||
self.peerReference = peerReference
|
||||
self.items = items
|
||||
self.availableLanguages = availableLanguages
|
||||
self.availableFolders = availableFolders
|
||||
self.pinnedIds = pinnedIds
|
||||
self.totalCount = totalCount
|
||||
self.loadMoreToken = loadMoreToken
|
||||
|
|
@ -621,12 +636,13 @@ public protocol StoryListContext: AnyObject {
|
|||
func loadMore(completion: (() -> Void)?)
|
||||
}
|
||||
|
||||
public final class PeerStoryListContext: StoryListContext {
|
||||
public final class PeerStoryListContext: StoryListContext {
|
||||
private final class Impl {
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let peerId: EnginePeer.Id
|
||||
private let isArchived: Bool
|
||||
private let folderId: Int64?
|
||||
|
||||
private let statePromise = Promise<State>()
|
||||
private var stateValue: State {
|
||||
|
|
@ -645,22 +661,40 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
|
||||
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.account = account
|
||||
self.peerId = peerId
|
||||
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)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
|
||||
guard let cached = cached else {
|
||||
return (nil, [], [], 0, [:], false)
|
||||
let cachedMain = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
|
||||
guard let cachedMain else {
|
||||
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 allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||
for storedItem in cached.items {
|
||||
|
|
@ -734,28 +768,30 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
|
||||
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
|
||||
guard let `self` = self else {
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] peerReference, items, pinnedIds, totalCount, allEntityFiles, folders, hasCache in
|
||||
guard let self else {
|
||||
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)
|
||||
updatedState.items.sort(by: { lhs, rhs in
|
||||
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 {
|
||||
return lhsPinned < rhsPinned
|
||||
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)
|
||||
if self.folderId == nil {
|
||||
updatedState.items.sort(by: { lhs, rhs in
|
||||
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 {
|
||||
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.loadMore(completion: nil)
|
||||
|
|
@ -791,6 +827,8 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
let account = self.account
|
||||
let accountPeerId = account.peerId
|
||||
let isArchived = self.isArchived
|
||||
let folderId = self.folderId
|
||||
let folders = self.stateValue.availableFolders
|
||||
self.requestDisposable = (self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|
|
@ -822,6 +860,73 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
var totalCount: Int = 0
|
||||
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 {
|
||||
case let .stories(_, count, stories, pinnedStories, chats, users):
|
||||
totalCount = Int(count)
|
||||
|
|
@ -883,7 +988,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
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: 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] updates in
|
||||
guard let `self` = self else {
|
||||
|
|
@ -1255,11 +1360,12 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
let items = finalUpdatedState.items
|
||||
let pinnedIds = finalUpdatedState.pinnedIds
|
||||
let totalCount = finalUpdatedState.totalCount
|
||||
let folders = finalUpdatedState.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))) {
|
||||
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()
|
||||
|
|
@ -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> {
|
||||
|
|
@ -1280,11 +1622,18 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
private let queue: Queue
|
||||
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()
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
self.folderId = folderId
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -1332,7 +1786,7 @@ public final class SearchStoryListContext: StoryListContext {
|
|||
self.account = account
|
||||
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.loadMore(completion: nil)
|
||||
|
|
@ -2149,7 +2603,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
|
||||
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)
|
||||
|
||||
|
|
@ -2166,6 +2620,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
peerReference: peer.flatMap(PeerReference.init),
|
||||
items: [],
|
||||
availableLanguages: [],
|
||||
availableFolders: [],
|
||||
pinnedIds: [],
|
||||
totalCount: 0,
|
||||
loadMoreToken: AnyHashable(0),
|
||||
|
|
@ -2313,6 +2768,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
peerReference: (peer?._asPeer()).flatMap(PeerReference.init),
|
||||
items: items,
|
||||
availableLanguages: availableLanguages,
|
||||
availableFolders: [],
|
||||
pinnedIds: [],
|
||||
totalCount: items.count,
|
||||
loadMoreToken: nil,
|
||||
|
|
@ -2413,6 +2869,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
peerReference: PeerReference(peer),
|
||||
items: items,
|
||||
availableLanguages: [],
|
||||
availableFolders: [],
|
||||
pinnedIds: [],
|
||||
totalCount: items.count,
|
||||
loadMoreToken: nil,
|
||||
|
|
@ -2592,6 +3049,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
peerReference: self.stateValue.peerReference,
|
||||
items: items,
|
||||
availableLanguages: [],
|
||||
availableFolders: [],
|
||||
pinnedIds: [],
|
||||
totalCount: items.count,
|
||||
loadMoreToken: nil,
|
||||
|
|
|
|||
|
|
@ -720,7 +720,7 @@ public extension StarGift {
|
|||
extension StarGift {
|
||||
init?(apiStarGift: Api.StarGift) {
|
||||
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()
|
||||
if (apiFlags & (1 << 2)) != 0 {
|
||||
flags.insert(.isBirthdayGift)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ swift_library(
|
|||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/TooltipUI",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -5,44 +5,62 @@ import ComponentFlow
|
|||
import MultilineTextComponent
|
||||
import TextLoadingEffect
|
||||
import ComponentDisplayAdapters
|
||||
import TooltipUI
|
||||
import AccountContext
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
public final class PeerInfoRatingComponent: Component {
|
||||
let context: AccountContext
|
||||
let backgroundColor: UIColor
|
||||
let foregroundColor: UIColor
|
||||
let tooltipBackgroundColor: UIColor
|
||||
let isExpanded: Bool
|
||||
let compactLabel: String
|
||||
let fraction: CGFloat
|
||||
let label: String
|
||||
let nextLabel: String
|
||||
let tooltipLabel: String
|
||||
let action: () -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
backgroundColor: UIColor,
|
||||
foregroundColor: UIColor,
|
||||
tooltipBackgroundColor: UIColor,
|
||||
isExpanded: Bool,
|
||||
compactLabel: String,
|
||||
fraction: CGFloat,
|
||||
label: String,
|
||||
nextLabel: String,
|
||||
tooltipLabel: String,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.backgroundColor = backgroundColor
|
||||
self.foregroundColor = foregroundColor
|
||||
self.tooltipBackgroundColor = tooltipBackgroundColor
|
||||
self.isExpanded = isExpanded
|
||||
self.compactLabel = compactLabel
|
||||
self.fraction = fraction
|
||||
self.label = label
|
||||
self.nextLabel = nextLabel
|
||||
self.tooltipLabel = tooltipLabel
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: PeerInfoRatingComponent, rhs: PeerInfoRatingComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.backgroundColor != rhs.backgroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.foregroundColor != rhs.foregroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.tooltipBackgroundColor != rhs.tooltipBackgroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
|
|
@ -58,6 +76,9 @@ public final class PeerInfoRatingComponent: Component {
|
|||
if lhs.nextLabel != rhs.nextLabel {
|
||||
return false
|
||||
}
|
||||
if lhs.tooltipLabel != rhs.tooltipLabel {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +98,8 @@ public final class PeerInfoRatingComponent: Component {
|
|||
|
||||
private var component: PeerInfoRatingComponent?
|
||||
|
||||
private var tooltipController: TooltipScreen?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -677,7 +677,7 @@ public func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, c
|
|||
|
||||
if case .user = inputData {
|
||||
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)
|
||||
|
||||
return ActionDisposable {
|
||||
|
|
@ -829,7 +829,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||
)
|
||||
|> 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
|
||||
|> map { state -> Bool? in
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
|> map { state -> Bool? in
|
||||
if !state.hasCache {
|
||||
|
|
@ -1201,7 +1201,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
let hasStoryArchive: Signal<Bool?, NoError>
|
||||
var storyArchiveListContext: StoryListContext?
|
||||
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
|
||||
hasStoryArchive = storyArchiveListContextValue.state
|
||||
|> map { state -> Bool? in
|
||||
|
|
@ -1535,7 +1535,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
let requestsContextPromise = Promise<PeerInvitationImportersContext?>(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
|
||||
|> map { state -> Bool? in
|
||||
if !state.hasCache {
|
||||
|
|
@ -1857,7 +1857,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
let storyListContext: StoryListContext?
|
||||
let hasStories: Signal<Bool?, NoError>
|
||||
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
|
||||
|> map { state -> Bool? in
|
||||
if !state.hasCache {
|
||||
|
|
|
|||
|
|
@ -1926,22 +1926,47 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.subtitleRating = subtitleRating
|
||||
}
|
||||
let fraction: CGFloat
|
||||
let tooltipLabel: String
|
||||
if let nextLevelStars = starRating.nextLevelStars {
|
||||
fraction = CGFloat(starRating.currentLevelStars) / CGFloat(nextLevelStars)
|
||||
tooltipLabel = "\(starRating.currentLevelStars) / \(nextLevelStars)"
|
||||
} else {
|
||||
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
|
||||
subtitleRatingSize = subtitleRating.update(
|
||||
transition: subtitleRatingTransition,
|
||||
component: AnyComponent(PeerInfoRatingComponent(
|
||||
backgroundColor: UIColor(white: 1.0, alpha: 0.1),
|
||||
foregroundColor: UIColor(white: 1.0, alpha: 1.0),
|
||||
context: self.context,
|
||||
backgroundColor: ratingBackgroundColor,
|
||||
foregroundColor: ratingForegroundColor,
|
||||
tooltipBackgroundColor: tooltipBackgroundColor,
|
||||
isExpanded: self.subtitleRatingIsExpanded,
|
||||
compactLabel: "\(starRating.level)",
|
||||
fraction: fraction,
|
||||
label: "Level \(starRating.level)",
|
||||
nextLabel: starRating.nextLevelStars != nil ? "\(starRating.level + 1)" : "",
|
||||
tooltipLabel: tooltipLabel,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
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)
|
||||
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
|
||||
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.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
|
||||
|
|
@ -2006,6 +2031,17 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
transition.updateFrameAdditive(view: subtitleBadgeView, frame: subtitleBadgeFrame)
|
||||
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 {
|
||||
let titleScale: CGFloat
|
||||
let subtitleScale: CGFloat
|
||||
|
|
@ -2048,7 +2084,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
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.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.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||
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(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
|
||||
if isAvatarExpanded {
|
||||
self.avatarListNode.listContainerNode.selectFirstItem()
|
||||
self.subtitleRatingIsExpanded = false
|
||||
}
|
||||
if case .animated = transition, !isAvatarExpanded {
|
||||
self.avatarListNode.animateAvatarCollapse(transition: transition)
|
||||
|
|
|
|||
|
|
@ -11496,26 +11496,150 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
private func displayMediaGalleryContextMenu(source: ContextReferenceContentNode, gesture: ContextGesture?) {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoStoryPaneNode else {
|
||||
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] = []
|
||||
|
||||
let strings = self.presentationData.strings
|
||||
|
||||
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)
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
let strings = self.presentationData.strings
|
||||
|
||||
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
|
||||
if ignoreNextActions {
|
||||
return
|
||||
|
|
@ -11527,92 +11651,96 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
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
|
||||
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()
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil)
|
||||
guard let localResult = strongSelf.hitTest(localPoint, with: nil) else {
|
||||
if let folder = pane.currentStoryFolder {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
self.mediaGalleryContextMenu = contextController
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
} else {
|
||||
let _ = (self.context.engine.data.get(EngineDataMap([
|
||||
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) {
|
||||
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:
|
||||
//if let data = self.data, let channel = data.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .sort, isForExpandedView: true))
|
||||
|
|
|
|||
|
|
@ -24,15 +24,18 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||
let context: AccountContext
|
||||
let peerId: EnginePeer.Id
|
||||
let scope: PeerInfoStoryGridScreen.Scope
|
||||
let selectionModeCompletion: (([EngineStoryItem]) -> Void)?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peerId: EnginePeer.Id,
|
||||
scope: PeerInfoStoryGridScreen.Scope
|
||||
scope: PeerInfoStoryGridScreen.Scope,
|
||||
selectionModeCompletion: (([EngineStoryItem]) -> Void)?
|
||||
) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.scope = scope
|
||||
self.selectionModeCompletion = selectionModeCompletion
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerInfoStoryGridScreenComponent, rhs: PeerInfoStoryGridScreenComponent) -> Bool {
|
||||
|
|
@ -342,11 +345,24 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||
}
|
||||
|
||||
let buttonText: String
|
||||
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
|
||||
var buttonIsEnabled = true
|
||||
if component.selectionModeCompletion != nil {
|
||||
//TODO:localize
|
||||
if self.selectedCount == 0 {
|
||||
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(
|
||||
|
|
@ -355,7 +371,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||
theme: environment.theme,
|
||||
title: buttonText,
|
||||
label: nil,
|
||||
isEnabled: true,
|
||||
isEnabled: buttonIsEnabled,
|
||||
insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: environment.safeInsets.bottom, right: sideInset),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
|
|
@ -365,6 +381,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||
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))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
|
|
@ -522,6 +544,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||
}
|
||||
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
|
||||
})
|
||||
|
||||
if component.selectionModeCompletion != nil {
|
||||
paneNode.shouldOpenItemsWhileInSelectionMode = false
|
||||
paneNode.setIsSelectionModeActive(true)
|
||||
}
|
||||
|
||||
applyState = true
|
||||
}
|
||||
|
||||
|
|
@ -562,6 +590,7 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
|||
|
||||
private let context: AccountContext
|
||||
private let scope: Scope
|
||||
private let selectionModeCompletion: (([EngineStoryItem]) -> Void)?
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
private var titleView: ChatTitleView?
|
||||
|
|
@ -573,15 +602,18 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
|||
public init(
|
||||
context: AccountContext,
|
||||
peerId: EnginePeer.Id,
|
||||
scope: Scope
|
||||
scope: Scope,
|
||||
selectionModeCompletion: (([EngineStoryItem]) -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.scope = scope
|
||||
self.selectionModeCompletion = selectionModeCompletion
|
||||
|
||||
super.init(context: context, component: PeerInfoStoryGridScreenComponent(
|
||||
context: context,
|
||||
peerId: peerId,
|
||||
scope: scope
|
||||
scope: scope,
|
||||
selectionModeCompletion: selectionModeCompletion
|
||||
), navigationBarAppearance: .default, theme: .default)
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||
|
|
@ -636,49 +668,54 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
|||
func updateTitle() {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
switch self.scope {
|
||||
case .saved:
|
||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View, let paneNode = componentView.paneNode else {
|
||||
return
|
||||
}
|
||||
let title: String?
|
||||
if componentView.selectedCount != 0 {
|
||||
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
||||
} else if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty {
|
||||
title = paneStatusText
|
||||
} else {
|
||||
title = nil
|
||||
}
|
||||
self.titleView?.titleContent = .custom(presentationData.strings.StoryList_TitleSaved, title, false)
|
||||
|
||||
if paneNode.isSelectionModeActive {
|
||||
self.navigationItem.setRightBarButton(self.doneBarButtonItem, animated: false)
|
||||
} else {
|
||||
self.navigationItem.setRightBarButton(self.moreBarButtonItem, animated: false)
|
||||
}
|
||||
case .archive:
|
||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
||||
return
|
||||
}
|
||||
let title: String
|
||||
if componentView.selectedCount != 0 {
|
||||
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
||||
} else {
|
||||
title = presentationData.strings.StoryList_TitleArchive
|
||||
}
|
||||
self.titleView?.titleContent = .custom(title, nil, false)
|
||||
|
||||
var hasMenu = false
|
||||
if componentView.selectedCount != 0 {
|
||||
hasMenu = true
|
||||
} else if let paneNode = componentView.paneNode, !paneNode.isEmpty {
|
||||
hasMenu = true
|
||||
}
|
||||
|
||||
if hasMenu {
|
||||
self.navigationItem.setRightBarButton(self.moreBarButtonItem, animated: false)
|
||||
} else {
|
||||
self.navigationItem.setRightBarButton(nil, animated: false)
|
||||
if self.selectionModeCompletion != nil {
|
||||
//TODO:localize
|
||||
self.titleView?.titleContent = .custom("Add Stories", nil, false)
|
||||
} else {
|
||||
switch self.scope {
|
||||
case .saved:
|
||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View, let paneNode = componentView.paneNode else {
|
||||
return
|
||||
}
|
||||
let title: String?
|
||||
if componentView.selectedCount != 0 {
|
||||
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
||||
} else if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty {
|
||||
title = paneStatusText
|
||||
} else {
|
||||
title = nil
|
||||
}
|
||||
self.titleView?.titleContent = .custom(presentationData.strings.StoryList_TitleSaved, title, false)
|
||||
|
||||
if paneNode.isSelectionModeActive {
|
||||
self.navigationItem.setRightBarButton(self.doneBarButtonItem, animated: false)
|
||||
} else {
|
||||
self.navigationItem.setRightBarButton(self.moreBarButtonItem, animated: false)
|
||||
}
|
||||
case .archive:
|
||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
||||
return
|
||||
}
|
||||
let title: String
|
||||
if componentView.selectedCount != 0 {
|
||||
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
||||
} else {
|
||||
title = presentationData.strings.StoryList_TitleArchive
|
||||
}
|
||||
self.titleView?.titleContent = .custom(title, nil, false)
|
||||
|
||||
var hasMenu = false
|
||||
if componentView.selectedCount != 0 {
|
||||
hasMenu = true
|
||||
} else if let paneNode = componentView.paneNode, !paneNode.isEmpty {
|
||||
hasMenu = true
|
||||
}
|
||||
|
||||
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/TelegramUI/Components/CheckComponent",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
|
||||
"//submodules/PromptUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import MultilineTextComponent
|
|||
import LocationUI
|
||||
import TabSelectorComponent
|
||||
import LanguageSelectionScreen
|
||||
import PromptUI
|
||||
import BottomButtonPanelComponent
|
||||
|
||||
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
||||
private let mediaBadgeTextColor = UIColor.white
|
||||
|
|
@ -1543,7 +1545,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let scope: Scope
|
||||
public let scope: Scope
|
||||
private let isProfileEmbedded: Bool
|
||||
private let canManageStories: Bool
|
||||
|
||||
|
|
@ -1568,7 +1570,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
private var mapInfoNode: LocationInfoListItemNode?
|
||||
private var searchHeader: ComponentView<Empty>?
|
||||
|
||||
private var botPreviewLanguageTab: ComponentView<Empty>?
|
||||
private var folderTab: ComponentView<Empty>?
|
||||
private var botPreviewFooter: ComponentView<Empty>?
|
||||
|
||||
private var barBackgroundLayer: SimpleLayer?
|
||||
|
|
@ -1583,6 +1585,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
private var didUpdateItemsOnce: Bool = false
|
||||
|
||||
private var selectionPanel: ComponentView<Empty>?
|
||||
private var actionPanel: ComponentView<Empty>?
|
||||
|
||||
private var isDeceleratingAfterTracking = false
|
||||
|
||||
|
|
@ -1627,6 +1630,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
|
||||
public var isEmptyUpdated: (Bool) -> Void = { _ in }
|
||||
|
||||
public var shouldOpenItemsWhileInSelectionMode: Bool = true
|
||||
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)?
|
||||
|
|
@ -1647,6 +1651,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
public var tabBarOffset: CGFloat {
|
||||
if case .botPreview = self.scope {
|
||||
return 0.0
|
||||
} else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
|
||||
return 0.0
|
||||
} else {
|
||||
return self.itemGrid.coveringInsetOffset
|
||||
}
|
||||
|
|
@ -1660,6 +1666,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
private var currentBotPreviewLanguages: [StoryListContext.State.Language] = []
|
||||
private var removedBotPreviewLanguages = Set<String>()
|
||||
|
||||
private var currentStoryFolders: [StoryListContext.State.Folder] = []
|
||||
private var removedStoryFolders = Set<Int64>()
|
||||
|
||||
private var numberOfItemsToRequest: Int = 50
|
||||
private var isRequestingView: Bool = false
|
||||
private var isFirstHistoryView: Bool = true
|
||||
|
|
@ -1674,7 +1683,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
private let maxBotPreviewCount: Int
|
||||
|
||||
private let defaultListSource: StoryListContext
|
||||
private var cachedListSources: [String: StoryListContext] = [:]
|
||||
private var cachedListSources: [AnyHashable: StoryListContext] = [:]
|
||||
|
||||
public var currentBotPreviewLanguage: (id: String, name: String)? {
|
||||
guard let listSource = self.listSource as? BotPreviewStoryListContext else {
|
||||
|
|
@ -1688,6 +1697,19 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
}
|
||||
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 paneDidScroll: (() -> Void)?
|
||||
|
|
@ -1749,7 +1771,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
} else {
|
||||
switch self.scope {
|
||||
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):
|
||||
self.listSource = SearchStoryListContext(account: context.account, source: .hashtag(peerId, query))
|
||||
case let .location(coordinates, venue):
|
||||
|
|
@ -1798,7 +1820,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
return
|
||||
}
|
||||
|
||||
if self.isProfileEmbedded {
|
||||
if self.isProfileEmbedded || !self.shouldOpenItemsWhileInSelectionMode {
|
||||
if let selectedIds = self.itemInteraction.selectedIds {
|
||||
self.itemInteraction.toggleSelection(item.story.id, !selectedIds.contains(item.story.id))
|
||||
return
|
||||
|
|
@ -2275,7 +2297,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false)
|
||||
|
||||
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 {
|
||||
|
|
@ -2447,6 +2469,122 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
var items: [ContextMenuItem] = []
|
||||
|
||||
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
|
||||
guard let self else {
|
||||
f(.default)
|
||||
|
|
@ -2784,6 +2922,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
}
|
||||
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
|
||||
if let currentListState = self.currentListState {
|
||||
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)
|
||||
|
||||
if self.currentBotPreviewLanguages != botPreviewLanguages || reloadAtTop {
|
||||
if self.currentBotPreviewLanguages != botPreviewLanguages || self.currentStoryFolders != storyFolders || reloadAtTop {
|
||||
self.currentBotPreviewLanguages = botPreviewLanguages
|
||||
self.currentStoryFolders = storyFolders
|
||||
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)
|
||||
}
|
||||
|
|
@ -2870,7 +3016,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
isReorderable = !item.storyItem.isPending
|
||||
case let .peer(id, _, _):
|
||||
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, _):
|
||||
if peerId != nil {
|
||||
|
|
@ -2963,11 +3113,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
if case .botPreview = self.scope {
|
||||
} else if case let .peer(id, _, _) = self.scope {
|
||||
if id == self.context.account.peerId {
|
||||
let maxPinnedIndex = items.items.lastIndex(where: { ($0 as? VisualMediaItem)?.isPinned == true })
|
||||
if let maxPinnedIndex {
|
||||
toIndex = min(toIndex, maxPinnedIndex)
|
||||
if self.currentStoryFolder != nil {
|
||||
} else {
|
||||
return
|
||||
let maxPinnedIndex = items.items.lastIndex(where: { ($0 as? VisualMediaItem)?.isPinned == true })
|
||||
if let maxPinnedIndex {
|
||||
toIndex = min(toIndex, maxPinnedIndex)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -3379,8 +3532,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
if let _ = self.mapNode {
|
||||
self.updateMapLayout(size: currentParams.size, topInset: currentParams.topInset, bottomInset: currentParams.bottomInset, deviceMetrics: currentParams.deviceMetrics, transition: transition)
|
||||
}
|
||||
if case .botPreview = self.scope, self.canManageStories {
|
||||
self.updateBotPreviewLanguageTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition)
|
||||
if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -3536,40 +3691,69 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
}
|
||||
}
|
||||
|
||||
private func updateBotPreviewLanguageTab(size: CGSize, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
guard case .botPreview = self.scope, self.canManageStories else {
|
||||
private func updateFolderTab(size: CGSize, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
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
|
||||
}
|
||||
|
||||
let botPreviewLanguageTab: ComponentView<Empty>
|
||||
if let current = self.botPreviewLanguageTab {
|
||||
botPreviewLanguageTab = current
|
||||
let folderTab: ComponentView<Empty>
|
||||
if let current = self.folderTab {
|
||||
folderTab = current
|
||||
} else {
|
||||
botPreviewLanguageTab = ComponentView()
|
||||
self.botPreviewLanguageTab = botPreviewLanguageTab
|
||||
folderTab = ComponentView()
|
||||
self.folderTab = folderTab
|
||||
}
|
||||
|
||||
var languageItems: [TabSelectorComponent.Item] = []
|
||||
languageItems.append(TabSelectorComponent.Item(
|
||||
var folderItems: [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"),
|
||||
title: self.presentationData.strings.BotPreviews_LanguageTab_Main
|
||||
title: mainTitle
|
||||
))
|
||||
for language in self.currentBotPreviewLanguages {
|
||||
languageItems.append(TabSelectorComponent.Item(
|
||||
id: AnyHashable(language.id),
|
||||
title: language.name
|
||||
))
|
||||
|
||||
if case .botPreview = self.scope {
|
||||
for language in self.currentBotPreviewLanguages {
|
||||
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"),
|
||||
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 {
|
||||
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),
|
||||
component: AnyComponent(TabSelectorComponent(
|
||||
colors: TabSelectorComponent.Colors(
|
||||
|
|
@ -3581,39 +3765,53 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
spacing: 9.0,
|
||||
verticalInset: 11.0
|
||||
),
|
||||
items: languageItems,
|
||||
selectedId: AnyHashable(selectedLanguageId),
|
||||
items: folderItems,
|
||||
selectedId: selectedId,
|
||||
setSelectedId: { [weak self] id in
|
||||
guard let self, let id = id.base as? String else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if id == "_add" {
|
||||
self.presentAddBotPreviewLanguage()
|
||||
} else if id == "_main" {
|
||||
self.setBotPreviewLanguage(id: nil, assumeEmpty: false)
|
||||
} else if let language = self.currentBotPreviewLanguages.first(where: { $0.id == id }) {
|
||||
self.setBotPreviewLanguage(id: language.id, assumeEmpty: false)
|
||||
if let id = id.base as? String {
|
||||
if id == "_add" {
|
||||
if case .botPreview = self.scope {
|
||||
self.presentAddBotPreviewLanguage()
|
||||
} else {
|
||||
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: {},
|
||||
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
|
||||
effectiveScrollingOffset = self.itemGrid.scrollingOffset
|
||||
botPreviewLanguageTabFrame.origin.y -= effectiveScrollingOffset
|
||||
folderTabFrame.origin.y -= effectiveScrollingOffset
|
||||
|
||||
let isSelectingOrReordering = self.isReordering || self.itemInteraction.selectedIds != nil
|
||||
|
||||
if let botPreviewLanguageTabView = botPreviewLanguageTab.view {
|
||||
if botPreviewLanguageTabView.superview == nil {
|
||||
self.view.addSubview(botPreviewLanguageTabView)
|
||||
if let folderTabView = folderTab.view {
|
||||
if folderTabView.superview == nil {
|
||||
self.view.addSubview(folderTabView)
|
||||
}
|
||||
transition.updateFrame(view: botPreviewLanguageTabView, frame: botPreviewLanguageTabFrame)
|
||||
transition.updateAlpha(layer: botPreviewLanguageTabView.layer, alpha: isSelectingOrReordering ? 0.5 : 1.0)
|
||||
botPreviewLanguageTabView.isUserInteractionEnabled = !isSelectingOrReordering
|
||||
transition.updateFrame(view: folderTabView, frame: folderTabFrame)
|
||||
transition.updateAlpha(layer: folderTabView.layer, alpha: isSelectingOrReordering ? 0.5 : 1.0)
|
||||
folderTabView.isUserInteractionEnabled = !isSelectingOrReordering
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3742,15 +3940,24 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
var listBottomInset = bottomInset
|
||||
var bottomInset = bottomInset
|
||||
|
||||
var displayFolderTab = false
|
||||
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
|
||||
|
||||
updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition)
|
||||
if let botPreviewFooterView = self.botPreviewFooter?.view {
|
||||
if case .botPreview = self.scope {
|
||||
updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition)
|
||||
if let botPreviewFooterView = self.botPreviewFooter?.view {
|
||||
listBottomInset += 18.0 + botPreviewFooterView.bounds.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, case let .peer(peerId, _, isArchived) = self.scope {
|
||||
let selectionPanel: ComponentView<Empty>
|
||||
|
|
@ -3930,13 +4137,200 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
} else if let selectionPanel = self.selectionPanel {
|
||||
self.selectionPanel = nil
|
||||
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)))
|
||||
|
||||
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>
|
||||
var emptyStateTransition = ComponentTransition(transition)
|
||||
if let current = self.emptyStateView {
|
||||
|
|
@ -3946,29 +4340,44 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
emptyStateView = ComponentView()
|
||||
self.emptyStateView = emptyStateView
|
||||
}
|
||||
|
||||
var isMainLanguage = true
|
||||
if let listSource = self.listSource as? BotPreviewStoryListContext, let _ = listSource.language {
|
||||
isMainLanguage = false
|
||||
}
|
||||
|
||||
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,
|
||||
animationName: nil,
|
||||
title: presentationData.strings.BotPreviews_Empty_Title,
|
||||
text: presentationData.strings.BotPreviews_Empty_Text(Int32(self.maxBotPreviewCount)),
|
||||
actionTitle: self.canManageStories ? presentationData.strings.BotPreviews_Empty_Add : nil,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
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
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.additionalEmptyAction?()
|
||||
}
|
||||
if isMainLanguage {
|
||||
self.presentAddBotPreviewLanguage()
|
||||
} else {
|
||||
self.presentDeleteBotPreviewLanguage()
|
||||
}
|
||||
},
|
||||
additionalActionSeparator: self.canManageStories ? presentationData.strings.BotPreviews_Empty_Separator : nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset)
|
||||
|
|
@ -3976,7 +4385,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
|
@ -3992,8 +4401,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
}
|
||||
|
||||
let backgroundColor: UIColor
|
||||
if self.isProfileEmbedded {
|
||||
backgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||
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
|
||||
}
|
||||
|
|
@ -4003,7 +4414,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
} else {
|
||||
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>
|
||||
var emptyStateTransition = ComponentTransition(transition)
|
||||
if let current = self.emptyStateView {
|
||||
|
|
@ -4073,6 +4484,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
let backgroundColor: UIColor
|
||||
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||
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 {
|
||||
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||
} else {
|
||||
|
|
@ -4101,6 +4514,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
|
||||
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||
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 {
|
||||
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.plainBackgroundColor)
|
||||
} else {
|
||||
|
|
@ -4109,6 +4524,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
} else {
|
||||
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||
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 {
|
||||
if case let .search(peerId, _) = self.scope, peerId != nil {
|
||||
|
||||
|
|
@ -4132,6 +4549,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
var adjustForSmallCount = true
|
||||
if case .botPreview = self.scope {
|
||||
adjustForSmallCount = false
|
||||
} else if self.currentStoryFolder != nil {
|
||||
adjustForSmallCount = false
|
||||
}
|
||||
|
||||
self.itemGrid.pinchEnabled = items.count > 2 && !self.isReordering
|
||||
|
|
@ -4234,10 +4653,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
}
|
||||
|
||||
public func canReorder() -> Bool {
|
||||
guard let items = self.items else {
|
||||
return false
|
||||
if case .botPreview = self.scope {
|
||||
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() {
|
||||
|
|
@ -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() {
|
||||
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: {
|
||||
|
|
@ -4335,12 +4837,32 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
}
|
||||
|
||||
if let id {
|
||||
if let cachedListSource = self.cachedListSources[id] {
|
||||
if let cachedListSource = self.cachedListSources[AnyHashable(id)] {
|
||||
self.listSource = cachedListSource
|
||||
} else {
|
||||
let listSource = BotPreviewStoryListContext(account: self.context.account, engine: self.context.engine, peerId: peerId, language: id, assumeEmpty: assumeEmpty)
|
||||
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 {
|
||||
self.listSource = self.defaultListSource
|
||||
|
|
@ -4388,20 +4910,26 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||
listSource.reorderItems(media: reorderedMedia)
|
||||
}
|
||||
} else if case let .peer(id, _, _) = self.scope, id == self.context.account.peerId, let items = self.items {
|
||||
var updatedPinnedIds: [Int32] = []
|
||||
for id in reorderedIds {
|
||||
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
|
||||
if let _ = self.currentStoryFolder {
|
||||
if let listSource = self.listSource as? PeerStoryListContext {
|
||||
listSource.reorderItemsInFolder(itemIds: reorderedIds.map { $0.id })
|
||||
}
|
||||
} else {
|
||||
var updatedPinnedIds: [Int32] = []
|
||||
for id in reorderedIds {
|
||||
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/SaveProgressScreen",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
|
||||
"//submodules/DirectMediaImageCache",
|
||||
"//submodules/PromptUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -1456,6 +1456,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
|||
}
|
||||
|
||||
private let context: AccountContext
|
||||
let listContext: StoryListContext
|
||||
|
||||
public private(set) var stateValue: StoryContentContextState?
|
||||
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) {
|
||||
self.context = context
|
||||
self.listContext = listContext
|
||||
|
||||
let preferHighQualityStories: Signal<Bool, NoError> = combineLatest(
|
||||
context.sharedContext.automaticMediaDownloadSettings
|
||||
|
|
|
|||
|
|
@ -1688,6 +1688,39 @@ private final class StoryContainerScreenComponent: Component {
|
|||
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
|
||||
return self?.environment?.controller()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import StoryFooterPanelComponent
|
|||
import TelegramNotices
|
||||
import SliderContextItem
|
||||
import SaveProgressScreen
|
||||
import DirectMediaImageCache
|
||||
import PromptUI
|
||||
|
||||
public final class StoryAvailableReactions: Equatable {
|
||||
let reactionItems: [ReactionItem]
|
||||
|
|
@ -120,6 +122,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
public let delete: () -> Void
|
||||
public let markAsSeen: (StoryId) -> Void
|
||||
public let reorder: () -> Void
|
||||
public let createToFolder: (String, [EngineStoryItem]) -> Void
|
||||
public let addToFolder: (Int64) -> Void
|
||||
public let controller: () -> ViewController?
|
||||
public let toggleAmbientMode: () -> Void
|
||||
public let keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>
|
||||
|
|
@ -157,6 +161,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
delete: @escaping () -> Void,
|
||||
markAsSeen: @escaping (StoryId) -> Void,
|
||||
reorder: @escaping () -> Void,
|
||||
createToFolder: @escaping (String, [EngineStoryItem]) -> Void,
|
||||
addToFolder: @escaping (Int64) -> Void,
|
||||
controller: @escaping () -> ViewController?,
|
||||
toggleAmbientMode: @escaping () -> Void,
|
||||
keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>,
|
||||
|
|
@ -193,6 +199,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
self.delete = delete
|
||||
self.markAsSeen = markAsSeen
|
||||
self.reorder = reorder
|
||||
self.createToFolder = createToFolder
|
||||
self.addToFolder = addToFolder
|
||||
self.controller = controller
|
||||
self.toggleAmbientMode = toggleAmbientMode
|
||||
self.keyboardInputData = keyboardInputData
|
||||
|
|
@ -6101,6 +6109,102 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
|
||||
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 {
|
||||
var speedValue: String = presentationData.strings.PlaybackSpeed_Normal
|
||||
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() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2546,6 +2546,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
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 {
|
||||
return archiveSettingsController(context: context)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ swift_library(
|
|||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/ShimmerEffect",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import BalancedTextComponent
|
|||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import ShimmerEffect
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
public enum TooltipActiveTextItem {
|
||||
case url(String, Bool)
|
||||
|
|
@ -1328,6 +1329,10 @@ public final class TooltipScreen: ViewController {
|
|||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if self.ignoreAppearanceMethodInvocations() {
|
||||
return
|
||||
}
|
||||
|
||||
self.controllerNode.animateIn()
|
||||
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