mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Stories
This commit is contained in:
parent
c01f7ce04a
commit
98f7c68432
@ -8527,12 +8527,13 @@ public extension Api.functions.stories {
|
||||
}
|
||||
}
|
||||
public extension Api.functions.stories {
|
||||
static func getExpiredStories(offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.Stories>) {
|
||||
static func getPinnedStories(userId: Api.InputUser, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.Stories>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(150442738)
|
||||
buffer.appendInt32(189206839)
|
||||
userId.serialize(buffer, true)
|
||||
serializeInt32(offsetId, buffer: buffer, boxed: false)
|
||||
serializeInt32(limit, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "stories.getExpiredStories", parameters: [("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in
|
||||
return (FunctionDescription(name: "stories.getPinnedStories", parameters: [("userId", String(describing: userId)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.stories.Stories?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -8543,13 +8544,12 @@ public extension Api.functions.stories {
|
||||
}
|
||||
}
|
||||
public extension Api.functions.stories {
|
||||
static func getPinnedStories(userId: Api.InputUser, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.Stories>) {
|
||||
static func getStoriesArchive(offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.Stories>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(189206839)
|
||||
userId.serialize(buffer, true)
|
||||
buffer.appendInt32(526108114)
|
||||
serializeInt32(offsetId, buffer: buffer, boxed: false)
|
||||
serializeInt32(limit, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "stories.getPinnedStories", parameters: [("userId", String(describing: userId)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in
|
||||
return (FunctionDescription(name: "stories.getStoriesArchive", parameters: [("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.stories.Stories?
|
||||
if let signature = reader.readInt32() {
|
||||
|
@ -464,7 +464,7 @@ public final class PeerStoryListContext {
|
||||
|
||||
let signal: Signal<Api.stories.Stories, MTRpcError>
|
||||
if isArchived {
|
||||
signal = account.network.request(Api.functions.stories.getExpiredStories(offsetId: Int32(loadMoreToken), limit: 100))
|
||||
signal = account.network.request(Api.functions.stories.getStoriesArchive(offsetId: Int32(loadMoreToken), limit: 100))
|
||||
} else {
|
||||
signal = account.network.request(Api.functions.stories.getPinnedStories(userId: inputUser, offsetId: Int32(loadMoreToken), limit: 100))
|
||||
}
|
||||
|
@ -881,6 +881,43 @@ public extension TelegramEngine {
|
||||
}
|
||||
}
|
||||
|
||||
public func refreshStoryViews(peerId: EnginePeer.Id, ids: [Int32]) -> Signal<Never, NoError> {
|
||||
if peerId != self.account.peerId {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
return _internal_getStoryViews(account: self.account, ids: ids)
|
||||
|> mapToSignal { views -> Signal<Never, NoError> in
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
var currentItems = transaction.getStoryItems(peerId: peerId)
|
||||
for i in 0 ..< currentItems.count {
|
||||
if ids.contains(currentItems[i].id) {
|
||||
if case let .item(item) = currentItems[i].value.get(Stories.StoredItem.self) {
|
||||
let updatedItem: Stories.StoredItem = .item(Stories.Item(
|
||||
id: item.id,
|
||||
timestamp: item.timestamp,
|
||||
expirationTimestamp: item.expirationTimestamp,
|
||||
media: item.media,
|
||||
text: item.text,
|
||||
entities: item.entities,
|
||||
views: views[currentItems[i].id],
|
||||
privacy: item.privacy,
|
||||
isPinned: item.isPinned,
|
||||
isExpired: item.isExpired,
|
||||
isPublic: item.isPublic
|
||||
))
|
||||
if let entry = CodableEntry(updatedItem) {
|
||||
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.setStoryItems(peerId: peerId, items: currentItems)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
|
||||
public func uploadStory(media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, period: Int) -> Signal<StoryUploadResult, NoError> {
|
||||
return _internal_uploadStory(account: self.account, media: media, text: text, entities: entities, pin: pin, privacy: privacy, period: period)
|
||||
}
|
||||
|
@ -371,6 +371,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/FullScreenEffectView",
|
||||
"//submodules/TelegramUI/Components/ShareWithPeersScreen",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
|
@ -151,9 +151,11 @@ public final class ChatListNavigationBar: Component {
|
||||
|
||||
private var applyScrollFractionAnimator: DisplayLinkAnimator?
|
||||
private var applyScrollFraction: CGFloat = 1.0
|
||||
private var storiesOffsetStartFraction: CGFloat = 1.0
|
||||
private var applyScrollUnlockedFraction: CGFloat = 1.0
|
||||
private var applyScrollStartFraction: CGFloat = 0.0
|
||||
private var storiesOffsetFraction: CGFloat = 0.0
|
||||
private var storiesUnlockedFraction: CGFloat = 0.0
|
||||
private var storiesUnlockedStartFraction: CGFloat = 1.0
|
||||
|
||||
private var tabsNode: ASDisplayNode?
|
||||
private var tabsNodeIsSearch: Bool = false
|
||||
@ -298,8 +300,8 @@ public final class ChatListNavigationBar: Component {
|
||||
}
|
||||
|
||||
if self.applyScrollFractionAnimator != nil {
|
||||
storiesOffsetFraction = self.applyScrollFraction * storiesOffsetFraction + (1.0 - self.applyScrollFraction) * 1.0
|
||||
storiesUnlockedOffsetFraction = self.applyScrollUnlockedFraction * storiesUnlockedOffsetFraction + (1.0 - self.applyScrollUnlockedFraction) * 1.0
|
||||
storiesOffsetFraction = self.applyScrollFraction * storiesOffsetFraction + (1.0 - self.applyScrollFraction) * self.storiesOffsetStartFraction
|
||||
storiesUnlockedOffsetFraction = self.applyScrollUnlockedFraction * storiesUnlockedOffsetFraction + (1.0 - self.applyScrollUnlockedFraction) * self.storiesUnlockedStartFraction
|
||||
}
|
||||
|
||||
let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight)
|
||||
@ -322,6 +324,8 @@ public final class ChatListNavigationBar: Component {
|
||||
}
|
||||
|
||||
self.storiesOffsetFraction = storiesOffsetFraction
|
||||
self.storiesUnlockedFraction = storiesUnlockedOffsetFraction
|
||||
|
||||
let headerContentSize = self.headerContent.update(
|
||||
transition: headerTransition,
|
||||
component: AnyComponent(ChatListHeaderComponent(
|
||||
@ -490,19 +494,25 @@ public final class ChatListNavigationBar: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if storiesUnlockedUpdated && component.storiesUnlocked {
|
||||
self.applyScrollStartFraction = 0.0
|
||||
let startFraction = self.storiesOffsetFraction
|
||||
self.applyScrollFraction = (1.0 - 0.0) * startFraction + 0.0 * 1.0
|
||||
if storiesUnlockedUpdated, case let .curve(duration, _) = transition.animation {
|
||||
self.applyScrollFractionAnimator?.invalidate()
|
||||
self.applyScrollFractionAnimator = nil
|
||||
|
||||
self.storiesOffsetStartFraction = self.storiesOffsetFraction
|
||||
self.storiesUnlockedStartFraction = self.storiesUnlockedFraction
|
||||
|
||||
let storiesUnlocked = component.storiesUnlocked
|
||||
|
||||
self.applyScrollFraction = 0.0
|
||||
self.applyScrollUnlockedFraction = 0.0
|
||||
self.applyScrollFractionAnimator = DisplayLinkAnimator(duration: 0.3, from: 0.0, to: 1.0, update: { [weak self] value in
|
||||
self.applyScrollFractionAnimator = DisplayLinkAnimator(duration: duration * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let t = listViewAnimationCurveSystem(value)
|
||||
self.applyScrollFraction = (1.0 - t) * startFraction + t * 1.0
|
||||
self.applyScrollUnlockedFraction = t
|
||||
self.applyScrollFraction = t
|
||||
self.applyScrollUnlockedFraction = storiesUnlocked ? t : (1.0 - t)
|
||||
|
||||
if let rawScrollOffset = self.rawScrollOffset {
|
||||
self.hasDeferredScrollOffset = true
|
||||
|
@ -0,0 +1,29 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PeerInfoStoryGridScreen",
|
||||
module_name = "PeerInfoStoryGridScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode",
|
||||
"//submodules/TelegramUI/Components/ChatListHeaderComponent",
|
||||
"//submodules/ContextUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,360 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import PeerInfoVisualMediaPaneNode
|
||||
import ViewControllerComponent
|
||||
import ChatListHeaderComponent
|
||||
import ContextUI
|
||||
|
||||
final class PeerInfoStoryGridScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let peerId: EnginePeer.Id
|
||||
let scope: PeerInfoStoryGridScreen.Scope
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peerId: EnginePeer.Id,
|
||||
scope: PeerInfoStoryGridScreen.Scope
|
||||
) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.scope = scope
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerInfoStoryGridScreenComponent, rhs: PeerInfoStoryGridScreenComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.peerId != rhs.peerId {
|
||||
return false
|
||||
}
|
||||
if lhs.scope != rhs.scope {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: PeerInfoStoryGridScreenComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var environment: EnvironmentType?
|
||||
|
||||
private var paneNode: PeerInfoStoryPaneNode?
|
||||
|
||||
private weak var mediaGalleryContextMenu: ContextController?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func morePressed(source: ContextReferenceContentNode) {
|
||||
guard let component = self.component, let controller = self.environment?.controller(), let pane = self.paneNode else {
|
||||
return
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
var recurseGenerateAction: ((Bool) -> ContextMenuActionItem)?
|
||||
let generateAction: (Bool) -> ContextMenuActionItem = { [weak pane] isZoomIn in
|
||||
let nextZoomLevel = isZoomIn ? pane?.availableZoomLevels().increment : pane?.availableZoomLevels().decrement
|
||||
let canZoom: Bool = nextZoomLevel != nil
|
||||
|
||||
return ContextMenuActionItem(id: isZoomIn ? 0 : 1, text: isZoomIn ? strings.SharedMedia_ZoomIn : strings.SharedMedia_ZoomOut, textColor: canZoom ? .primary : .disabled, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
|
||||
}, action: canZoom ? { action in
|
||||
guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else {
|
||||
return
|
||||
}
|
||||
pane.updateZoomLevel(level: zoomLevel)
|
||||
if let recurseGenerateAction = recurseGenerateAction {
|
||||
action.updateAction(0, recurseGenerateAction(true))
|
||||
action.updateAction(1, recurseGenerateAction(false))
|
||||
}
|
||||
} : nil)
|
||||
}
|
||||
recurseGenerateAction = { isZoomIn in
|
||||
return generateAction(isZoomIn)
|
||||
}
|
||||
|
||||
items.append(.action(generateAction(true)))
|
||||
items.append(.action(generateAction(false)))
|
||||
|
||||
if component.peerId == component.context.account.peerId, case .saved = component.scope {
|
||||
var ignoreNextActions = false
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Show Archive", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/StoryArchive"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
if ignoreNextActions {
|
||||
return
|
||||
}
|
||||
ignoreNextActions = true
|
||||
a(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
self.environment?.controller()?.push(PeerInfoStoryGridScreen(context: component.context, peerId: component.peerId, scope: .archive))
|
||||
})))
|
||||
}
|
||||
|
||||
/*if photoCount != 0 && videoCount != 0 {
|
||||
items.append(.separator)
|
||||
|
||||
let showPhotos: Bool
|
||||
switch pane.contentType {
|
||||
case .photo, .photoOrVideo:
|
||||
showPhotos = true
|
||||
default:
|
||||
showPhotos = false
|
||||
}
|
||||
let showVideos: Bool
|
||||
switch pane.contentType {
|
||||
case .video, .photoOrVideo:
|
||||
showVideos = true
|
||||
default:
|
||||
showVideos = false
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowPhotos, icon: { theme in
|
||||
if !showPhotos {
|
||||
return nil
|
||||
}
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak pane] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let pane = pane else {
|
||||
return
|
||||
}
|
||||
let updatedContentType: PeerInfoVisualMediaPaneNode.ContentType
|
||||
switch pane.contentType {
|
||||
case .photoOrVideo:
|
||||
updatedContentType = .video
|
||||
case .photo:
|
||||
updatedContentType = .photo
|
||||
case .video:
|
||||
updatedContentType = .photoOrVideo
|
||||
default:
|
||||
updatedContentType = pane.contentType
|
||||
}
|
||||
pane.updateContentType(contentType: updatedContentType)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowVideos, icon: { theme in
|
||||
if !showVideos {
|
||||
return nil
|
||||
}
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak pane] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let pane = pane else {
|
||||
return
|
||||
}
|
||||
let updatedContentType: PeerInfoVisualMediaPaneNode.ContentType
|
||||
switch pane.contentType {
|
||||
case .photoOrVideo:
|
||||
updatedContentType = .photo
|
||||
case .photo:
|
||||
updatedContentType = .photoOrVideo
|
||||
case .video:
|
||||
updatedContentType = .video
|
||||
default:
|
||||
updatedContentType = pane.contentType
|
||||
}
|
||||
pane.updateContentType(contentType: updatedContentType)
|
||||
})))
|
||||
}*/
|
||||
|
||||
let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
||||
contextController.passthroughTouchEvent = { [weak self] sourceView, point in
|
||||
guard let self else {
|
||||
return .ignore
|
||||
}
|
||||
|
||||
let localPoint = self.convert(sourceView.convert(point, to: nil), from: nil)
|
||||
guard let localResult = self.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? 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)
|
||||
}
|
||||
|
||||
func update(component: PeerInfoStoryGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let environment = environment[EnvironmentType.self].value
|
||||
|
||||
let themeUpdated = self.environment?.theme !== environment.theme
|
||||
|
||||
self.environment = environment
|
||||
|
||||
if themeUpdated {
|
||||
self.backgroundColor = environment.theme.list.plainBackgroundColor
|
||||
}
|
||||
|
||||
let paneNode: PeerInfoStoryPaneNode
|
||||
if let current = self.paneNode {
|
||||
paneNode = current
|
||||
} else {
|
||||
paneNode = PeerInfoStoryPaneNode(
|
||||
context: component.context,
|
||||
peerId: component.peerId,
|
||||
chatLocation: .peer(id: component.peerId),
|
||||
contentType: .photoOrVideo,
|
||||
captureProtected: false,
|
||||
isArchive: component.scope == .archive,
|
||||
navigationController: { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
return self.environment?.controller()?.navigationController as? NavigationController
|
||||
}
|
||||
)
|
||||
self.paneNode = paneNode
|
||||
self.addSubview(paneNode.view)
|
||||
}
|
||||
|
||||
paneNode.update(
|
||||
size: availableSize,
|
||||
topInset: environment.navigationHeight,
|
||||
sideInset: environment.safeInsets.left,
|
||||
bottomInset: environment.safeInsets.bottom,
|
||||
visibleHeight: availableSize.height,
|
||||
isScrollingLockedAtTop: false,
|
||||
expandProgress: 1.0,
|
||||
presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }),
|
||||
synchronous: false,
|
||||
transition: transition.containedViewLayoutTransition
|
||||
)
|
||||
transition.setFrame(view: paneNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
||||
public enum Scope {
|
||||
case saved
|
||||
case archive
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
private var moreBarButton: MoreHeaderButton?
|
||||
private var moreBarButtonItem: UIBarButtonItem?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
peerId: EnginePeer.Id,
|
||||
scope: Scope
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: PeerInfoStoryGridScreenComponent(
|
||||
context: context,
|
||||
peerId: peerId,
|
||||
scope: scope
|
||||
), navigationBarAppearance: .default, theme: .default)
|
||||
|
||||
//TODO:localize
|
||||
self.navigationItem.title = "My Stories"
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||
let moreBarButton = MoreHeaderButton(color: presentationData.theme.rootController.navigationBar.buttonColor)
|
||||
moreBarButton.isUserInteractionEnabled = true
|
||||
self.moreBarButton = moreBarButton
|
||||
|
||||
moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: presentationData.theme.rootController.navigationBar.buttonColor)))
|
||||
let moreBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
|
||||
self.moreBarButtonItem = moreBarButtonItem
|
||||
moreBarButton.contextAction = { [weak self] sourceNode, gesture in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
}
|
||||
moreBarButton.addTarget(self, action: #selector(self.morePressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.navigationItem.setRightBarButton(moreBarButtonItem, animated: false)
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
@objc private func morePressed() {
|
||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
||||
return
|
||||
}
|
||||
guard let moreBarButton = self.moreBarButton else {
|
||||
return
|
||||
}
|
||||
componentView.morePressed(source: moreBarButton.referenceNode)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerInfoContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
@ -23,7 +23,6 @@ import TelegramNotices
|
||||
import TelegramUIPreferences
|
||||
import CheckNode
|
||||
import AppBundle
|
||||
import ChatControllerInteraction
|
||||
import InvisibleInkDustNode
|
||||
import MediaPickerUI
|
||||
import StoryContainerScreen
|
||||
@ -33,20 +32,20 @@ private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
||||
private let mediaBadgeTextColor = UIColor.white
|
||||
|
||||
private final class VisualMediaItemInteraction {
|
||||
let openMessage: (Message) -> Void
|
||||
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
|
||||
let toggleSelection: (MessageId, Bool) -> Void
|
||||
let openItem: (EngineStoryItem) -> Void
|
||||
let openItemContextActions: (EngineStoryItem, ASDisplayNode, CGRect, ContextGesture?) -> Void
|
||||
let toggleSelection: (Int32, Bool) -> Void
|
||||
|
||||
var hiddenMedia: [MessageId: [Media]] = [:]
|
||||
var selectedMessageIds: Set<MessageId>?
|
||||
var hiddenMedia = Set<Int32>()
|
||||
var selectedIds: Set<Int32>?
|
||||
|
||||
init(
|
||||
openMessage: @escaping (Message) -> Void,
|
||||
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
||||
toggleSelection: @escaping (MessageId, Bool) -> Void
|
||||
openItem: @escaping (EngineStoryItem) -> Void,
|
||||
openItemContextActions: @escaping (EngineStoryItem, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
||||
toggleSelection: @escaping (Int32, Bool) -> Void
|
||||
) {
|
||||
self.openMessage = openMessage
|
||||
self.openMessageContextActions = openMessageContextActions
|
||||
self.openItem = openItem
|
||||
self.openItemContextActions = openItemContextActions
|
||||
self.toggleSelection = toggleSelection
|
||||
}
|
||||
}
|
||||
@ -483,7 +482,6 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
||||
let directMediaImageCache: DirectMediaImageCache
|
||||
let captureProtected: Bool
|
||||
var strings: PresentationStrings
|
||||
let chatControllerInteraction: ChatControllerInteraction
|
||||
var chatPresentationData: ChatPresentationData
|
||||
var checkNodeTheme: CheckNodeTheme
|
||||
|
||||
@ -500,10 +498,9 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
||||
|
||||
private var shimmerImages: [CGFloat: UIImage] = [:]
|
||||
|
||||
init(context: AccountContext, chatLocation: ChatLocation, chatControllerInteraction: ChatControllerInteraction, directMediaImageCache: DirectMediaImageCache, captureProtected: Bool) {
|
||||
init(context: AccountContext, chatLocation: ChatLocation, directMediaImageCache: DirectMediaImageCache, captureProtected: Bool) {
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
self.chatControllerInteraction = chatControllerInteraction
|
||||
self.directMediaImageCache = directMediaImageCache
|
||||
self.captureProtected = false
|
||||
|
||||
@ -674,13 +671,8 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
||||
layer.updateDuration(duration: duration, isMin: isMin, minFactor: min(1.0, layer.bounds.height / 74.0))
|
||||
}
|
||||
|
||||
if let selectionState = self.chatControllerInteraction.selectionState {
|
||||
//TODO:selection
|
||||
let _ = selectionState
|
||||
layer.updateSelection(theme: self.checkNodeTheme, isSelected: false, animated: false)
|
||||
} else {
|
||||
layer.updateSelection(theme: self.checkNodeTheme, isSelected: nil, animated: false)
|
||||
}
|
||||
//TODO:selection
|
||||
layer.updateSelection(theme: self.checkNodeTheme, isSelected: nil, animated: false)
|
||||
|
||||
layer.bind(item: item)
|
||||
}
|
||||
@ -764,11 +756,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let chatLocation: ChatLocation
|
||||
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
|
||||
private let chatControllerInteraction: ChatControllerInteraction
|
||||
private let isArchive: Bool
|
||||
public private(set) var contentType: ContentType
|
||||
private var contentTypePromise: ValuePromise<ContentType>
|
||||
|
||||
private let navigationController: () -> NavigationController?
|
||||
|
||||
public weak var parentController: ViewController?
|
||||
|
||||
private let contextGestureContainerNode: ContextControllerSourceNode
|
||||
@ -825,14 +818,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, contentType: ContentType, captureProtected: Bool) {
|
||||
public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.chatLocation = chatLocation
|
||||
self.chatLocationContextHolder = chatLocationContextHolder
|
||||
self.chatControllerInteraction = chatControllerInteraction
|
||||
self.contentType = contentType
|
||||
self.contentTypePromise = ValuePromise<ContentType>(contentType)
|
||||
self.navigationController = navigationController
|
||||
self.isArchive = isArchive
|
||||
|
||||
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -843,12 +836,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
self.itemGridBinding = SparseItemGridBindingImpl(
|
||||
context: context,
|
||||
chatLocation: .peer(id: peerId),
|
||||
chatControllerInteraction: chatControllerInteraction,
|
||||
directMediaImageCache: self.directMediaImageCache,
|
||||
captureProtected: captureProtected
|
||||
)
|
||||
|
||||
self.listSource = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
|
||||
self.listSource = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: self.isArchive)
|
||||
self.calendarSource = nil
|
||||
|
||||
super.init()
|
||||
@ -879,155 +871,75 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let selectionState = self.chatControllerInteraction.selectionState {
|
||||
let _ = selectionState
|
||||
//TODO:selection
|
||||
/*var toggledValue = true
|
||||
if selectionState.selectedIds.contains(item.message.id) {
|
||||
toggledValue = false
|
||||
//TODO:selection
|
||||
let listContext = PeerStoryListContentContextImpl(
|
||||
context: self.context,
|
||||
peerId: self.peerId,
|
||||
listContext: self.listSource,
|
||||
initialId: item.story.id
|
||||
)
|
||||
let _ = (listContext.state
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self, let navigationController = self.navigationController() else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatControllerInteraction.toggleMessagesSelection([item.message.id], toggledValue)*/
|
||||
} else {
|
||||
let listContext = PeerStoryListContentContextImpl(
|
||||
context: self.context,
|
||||
peerId: self.peerId,
|
||||
listContext: self.listSource,
|
||||
initialId: item.story.id
|
||||
)
|
||||
let _ = (listContext.state
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self, let navigationController = self.chatControllerInteraction.navigationController() else {
|
||||
return
|
||||
}
|
||||
|
||||
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||
|
||||
let story = item.story
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
if let listItem = itemLayer.item, listItem.story.id == story.id {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
}
|
||||
if let foundItemLayer {
|
||||
let itemRect = self.itemGrid.frameForItem(layer: foundItemLayer)
|
||||
transitionIn = StoryContainerScreen.TransitionIn(
|
||||
sourceView: self.view,
|
||||
sourceRect: self.itemGrid.view.convert(itemRect, to: self.view),
|
||||
sourceCornerRadius: 0.0
|
||||
)
|
||||
}
|
||||
|
||||
let storyContainerScreen = StoryContainerScreen(
|
||||
context: self.context,
|
||||
content: listContext,
|
||||
transitionIn: transitionIn,
|
||||
transitionOut: { [weak self] _, itemId in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
if let listItem = itemLayer.item, AnyHashable(listItem.story.id) == itemId {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
}
|
||||
if let foundItemLayer {
|
||||
let itemRect = self.itemGrid.frameForItem(layer: foundItemLayer)
|
||||
return StoryContainerScreen.TransitionOut(
|
||||
destinationView: self.view,
|
||||
destinationRect: self.itemGrid.view.convert(itemRect, to: self.view),
|
||||
destinationCornerRadius: 0.0,
|
||||
destinationIsAvatar: false,
|
||||
completed: {}
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
)
|
||||
navigationController.pushViewController(storyContainerScreen)
|
||||
})
|
||||
|
||||
/*let _ = (StoryChatContent.stories(
|
||||
context: self.context,
|
||||
storyList: self.listSource,
|
||||
focusItem: item.story.id
|
||||
)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] initialContent in
|
||||
guard let self, let navigationController = self.chatControllerInteraction.navigationController() else {
|
||||
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||
|
||||
let story = item.story
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||
|
||||
let story = item.story
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
if let listItem = itemLayer.item, listItem.story.id == story.id {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
if let listItem = itemLayer.item, listItem.story.id == story.id {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
if let foundItemLayer {
|
||||
let itemRect = self.itemGrid.frameForItem(layer: foundItemLayer)
|
||||
transitionIn = StoryContainerScreen.TransitionIn(
|
||||
sourceView: self.view,
|
||||
sourceRect: self.itemGrid.view.convert(itemRect, to: self.view),
|
||||
sourceCornerRadius: 0.0
|
||||
)
|
||||
}
|
||||
|
||||
let storyContainerScreen = StoryContainerScreen(
|
||||
context: self.context,
|
||||
initialFocusedId: AnyHashable(peerId),
|
||||
initialContent: initialContent,
|
||||
transitionIn: transitionIn,
|
||||
transitionOut: { [weak self] _, itemId in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
if let listItem = itemLayer.item, AnyHashable(listItem.story.id) == itemId {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
}
|
||||
if let foundItemLayer {
|
||||
let itemRect = self.itemGrid.frameForItem(layer: foundItemLayer)
|
||||
return StoryContainerScreen.TransitionOut(
|
||||
destinationView: self.view,
|
||||
destinationRect: self.itemGrid.view.convert(itemRect, to: self.view),
|
||||
destinationCornerRadius: 0.0,
|
||||
destinationIsAvatar: false,
|
||||
completed: {}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
if let foundItemLayer {
|
||||
let itemRect = self.itemGrid.frameForItem(layer: foundItemLayer)
|
||||
transitionIn = StoryContainerScreen.TransitionIn(
|
||||
sourceView: self.view,
|
||||
sourceRect: self.itemGrid.view.convert(itemRect, to: self.view),
|
||||
sourceCornerRadius: 0.0
|
||||
)
|
||||
}
|
||||
|
||||
let storyContainerScreen = StoryContainerScreen(
|
||||
context: self.context,
|
||||
content: listContext,
|
||||
transitionIn: transitionIn,
|
||||
transitionOut: { [weak self] _, itemId in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
)
|
||||
navigationController.pushViewController(storyContainerScreen)
|
||||
})*/
|
||||
//TODO:open
|
||||
//let _ = strongSelf.chatControllerInteraction.openMessage(item.message, .default)
|
||||
}
|
||||
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
if let listItem = itemLayer.item, AnyHashable(listItem.story.id) == itemId {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
}
|
||||
if let foundItemLayer {
|
||||
let itemRect = self.itemGrid.frameForItem(layer: foundItemLayer)
|
||||
return StoryContainerScreen.TransitionOut(
|
||||
destinationView: self.view,
|
||||
destinationRect: self.itemGrid.view.convert(itemRect, to: self.view),
|
||||
destinationCornerRadius: 0.0,
|
||||
destinationIsAvatar: false,
|
||||
completed: {}
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
)
|
||||
navigationController.pushViewController(storyContainerScreen)
|
||||
})
|
||||
}
|
||||
|
||||
self.itemGridBinding.onTagTapImpl = { [weak self] in
|
||||
@ -1118,17 +1030,27 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
|
||||
self._itemInteraction = VisualMediaItemInteraction(
|
||||
openMessage: { [weak self] message in
|
||||
let _ = self?.chatControllerInteraction.openMessage(message, .default)
|
||||
openItem: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
},
|
||||
openMessageContextActions: { [weak self] message, sourceNode, sourceRect, gesture in
|
||||
self?.chatControllerInteraction.openMessageContextActions(message, sourceNode, sourceRect, gesture)
|
||||
openItemContextActions: { [weak self] item, sourceNode, sourceRect, gesture in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
},
|
||||
toggleSelection: { [weak self] id, value in
|
||||
self?.chatControllerInteraction.toggleMessagesSelection([id], value)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
}
|
||||
)
|
||||
self.itemInteraction.selectedMessageIds = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
|
||||
//TODO:selection
|
||||
//self.itemInteraction.selectedItemIds =
|
||||
|
||||
self.contextGestureContainerNode.isGestureEnabled = true
|
||||
self.contextGestureContainerNode.addSubnode(self.itemGrid)
|
||||
@ -1664,78 +1586,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
|
||||
private func handlePanSelection(location: CGPoint) {
|
||||
/*var location = location
|
||||
if location.y < 0.0 {
|
||||
location.y = 5.0
|
||||
} else if location.y > self.frame.height {
|
||||
location.y = self.frame.height - 5.0
|
||||
}
|
||||
|
||||
var hasState = false
|
||||
if let state = self.selectionPanState {
|
||||
hasState = true
|
||||
if let message = self.messageAtPoint(location) {
|
||||
if message.id == state.initialMessageId {
|
||||
if !state.toggledMessageIds.isEmpty {
|
||||
self.chatControllerInteraction.toggleMessagesSelection(state.toggledMessageIds.flatMap { $0.compactMap({ $0 }) }, !state.selecting)
|
||||
self.selectionPanState = (state.selecting, state.initialMessageId, [])
|
||||
}
|
||||
} else if state.toggledMessageIds.last?.first != message.id {
|
||||
var updatedToggledMessageIds: [[EngineMessage.Id]] = []
|
||||
var previouslyToggled = false
|
||||
for i in (0 ..< state.toggledMessageIds.count) {
|
||||
if let messageId = state.toggledMessageIds[i].first {
|
||||
if messageId == message.id {
|
||||
previouslyToggled = true
|
||||
updatedToggledMessageIds = Array(state.toggledMessageIds.prefix(i + 1))
|
||||
|
||||
let messageIdsToToggle = Array(state.toggledMessageIds.suffix(state.toggledMessageIds.count - i - 1)).flatMap { $0 }
|
||||
self.chatControllerInteraction.toggleMessagesSelection(messageIdsToToggle, !state.selecting)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !previouslyToggled {
|
||||
updatedToggledMessageIds = state.toggledMessageIds
|
||||
let isSelected = self.chatControllerInteraction.selectionState?.selectedIds.contains(message.id) ?? false
|
||||
if state.selecting != isSelected {
|
||||
updatedToggledMessageIds.append([message.id])
|
||||
self.chatControllerInteraction.toggleMessagesSelection([message.id], state.selecting)
|
||||
}
|
||||
}
|
||||
|
||||
self.selectionPanState = (state.selecting, state.initialMessageId, updatedToggledMessageIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
guard hasState else {
|
||||
return
|
||||
}
|
||||
let scrollingAreaHeight: CGFloat = 50.0
|
||||
if location.y < scrollingAreaHeight || location.y > self.frame.height - scrollingAreaHeight {
|
||||
if location.y < self.frame.height / 2.0 {
|
||||
self.selectionScrollDelta = (scrollingAreaHeight - location.y) / scrollingAreaHeight
|
||||
} else {
|
||||
self.selectionScrollDelta = -(scrollingAreaHeight - min(scrollingAreaHeight, max(0.0, (self.frame.height - location.y)))) / scrollingAreaHeight
|
||||
}
|
||||
if let displayLink = self.selectionScrollDisplayLink {
|
||||
displayLink.isPaused = false
|
||||
} else {
|
||||
if let _ = self.selectionScrollActivationTimer {
|
||||
} else {
|
||||
let timer = SwiftSignalKit.Timer(timeout: 0.45, repeat: false, completion: { [weak self] in
|
||||
self?.setupSelectionScrolling()
|
||||
}, queue: .mainQueue())
|
||||
timer.start()
|
||||
self.selectionScrollActivationTimer = timer
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.selectionScrollDisplayLink?.isPaused = true
|
||||
self.selectionScrollActivationTimer?.invalidate()
|
||||
self.selectionScrollActivationTimer = nil
|
||||
}*/
|
||||
}
|
||||
|
||||
private var selectionScrollSkipUpdate = false
|
||||
|
@ -340,6 +340,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
private var requestStoryDisposables = DisposableSet()
|
||||
|
||||
private var preloadStoryResourceDisposables: [MediaResourceId: Disposable] = [:]
|
||||
private var pollStoryMetadataDisposables = DisposableSet()
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -427,6 +428,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
for (_, disposable) in self.preloadStoryResourceDisposables {
|
||||
disposable.dispose()
|
||||
}
|
||||
self.pollStoryMetadataDisposables.dispose()
|
||||
}
|
||||
|
||||
private func updatePeerContexts() {
|
||||
@ -581,9 +583,18 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
self.updatedPromise.set(.single(Void()))
|
||||
|
||||
var possibleItems: [(EnginePeer, EngineStoryItem)] = []
|
||||
var pollItems: [StoryKey] = []
|
||||
if let slice = currentState.centralPeerContext.sliceValue {
|
||||
if slice.peer.id == self.context.account.peerId {
|
||||
pollItems.append(StoryKey(peerId: slice.peer.id, id: slice.item.storyItem.id))
|
||||
}
|
||||
|
||||
for item in currentState.centralPeerContext.nextItems {
|
||||
possibleItems.append((slice.peer, item))
|
||||
|
||||
if slice.peer.id == self.context.account.peerId {
|
||||
pollItems.append(StoryKey(peerId: slice.peer.id, id: item.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
if let nextPeerContext = currentState.nextPeerContext, let slice = nextPeerContext.sliceValue {
|
||||
@ -638,9 +649,6 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
if let size = info.size {
|
||||
fetchRange = (0 ..< Int64(size), .default)
|
||||
}
|
||||
#if DEBUG
|
||||
fetchRange = nil
|
||||
#endif
|
||||
self.preloadStoryResourceDisposables[resource.resource.id] = fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: resource, range: fetchRange).start()
|
||||
}
|
||||
}
|
||||
@ -655,6 +663,18 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
for id in removeIds {
|
||||
self.preloadStoryResourceDisposables.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
var pollIdByPeerId: [EnginePeer.Id: [Int32]] = [:]
|
||||
for storyKey in pollItems.prefix(3) {
|
||||
if pollIdByPeerId[storyKey.peerId] == nil {
|
||||
pollIdByPeerId[storyKey.peerId] = [storyKey.id]
|
||||
} else {
|
||||
pollIdByPeerId[storyKey.peerId]?.append(storyKey.id)
|
||||
}
|
||||
}
|
||||
for (peerId, ids) in pollIdByPeerId {
|
||||
self.pollStoryMetadataDisposables.add(self.context.engine.messages.refreshStoryViews(peerId: peerId, ids: ids).start())
|
||||
}
|
||||
}
|
||||
|
||||
public func resetSideStates() {
|
||||
|
@ -543,7 +543,7 @@ public final class StoryPeerListItemComponent: Component {
|
||||
} else if let mappedRightCenter {
|
||||
avatarPath.addEllipse(in: CGRect(origin: CGPoint(), size: avatarSize).insetBy(dx: -indicatorLineWidth, dy: -indicatorLineWidth).offsetBy(dx: abs(mappedRightCenter.x - indicatorCenter.x), dy: 0.0))
|
||||
}
|
||||
transition.setShapeLayerPath(layer: self.avatarShapeLayer, path: avatarPath)
|
||||
Transition.immediate.setShapeLayerPath(layer: self.avatarShapeLayer, path: avatarPath)
|
||||
|
||||
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineWidth * 0.5))
|
||||
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/StoryArchive.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/StoryArchive.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "StoryArchive.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.66496 0.999961C9.66496 0.632692 9.36723 0.334961 8.99996 0.334961C4.21442 0.334961 0.334961 4.21441 0.334961 8.99996C0.334961 13.7855 4.21442 17.665 8.99996 17.665C13.7855 17.665 17.665 13.7855 17.665 8.99996C17.665 5.90988 16.0476 3.1987 13.6148 1.66496H15.5C15.8672 1.66496 16.165 1.36723 16.165 0.999961C16.165 0.632692 15.8672 0.334961 15.5 0.334961H12C11.6327 0.334961 11.335 0.632692 11.335 0.999961V4.49996C11.335 4.86723 11.6327 5.16496 12 5.16496C12.3672 5.16496 12.665 4.86723 12.665 4.49996V2.64464C14.8596 3.91304 16.335 6.28482 16.335 8.99996C16.335 13.051 13.051 16.335 8.99996 16.335C4.94895 16.335 1.66496 13.051 1.66496 8.99996C1.66496 4.94895 4.94895 1.66496 8.99996 1.66496C9.36723 1.66496 9.66496 1.36723 9.66496 0.999961ZM9.66492 3.99997C9.66492 3.6327 9.36719 3.33497 8.99992 3.33497C8.63265 3.33497 8.33492 3.6327 8.33492 3.99997V8.99997C8.33492 9.17634 8.40499 9.34548 8.5297 9.4702L11.5297 12.4702C11.7894 12.7299 12.2105 12.7299 12.4701 12.4702C12.7298 12.2105 12.7298 11.7894 12.4701 11.5297L9.66492 8.72452V3.99997Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -368,7 +368,7 @@ private final class PeerInfoPendingPane {
|
||||
let paneNode: PeerInfoPaneNode
|
||||
switch key {
|
||||
case .stories:
|
||||
let visualPaneNode = PeerInfoStoryPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected)
|
||||
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isArchive: false, navigationController: chatControllerInteraction.navigationController)
|
||||
paneNode = visualPaneNode
|
||||
visualPaneNode.openCurrentDate = {
|
||||
openMediaCalendar()
|
||||
|
@ -87,6 +87,7 @@ import StorageUsageScreen
|
||||
import AvatarEditorScreen
|
||||
import SendInviteLinkScreen
|
||||
import PeerInfoVisualMediaPaneNode
|
||||
import PeerInfoStoryGridScreen
|
||||
|
||||
enum PeerInfoAvatarEditingMode {
|
||||
case generic
|
||||
@ -456,6 +457,7 @@ private enum PeerInfoSettingsSection {
|
||||
case avatar
|
||||
case edit
|
||||
case proxy
|
||||
case stories
|
||||
case savedMessages
|
||||
case recentCalls
|
||||
case devices
|
||||
@ -655,6 +657,7 @@ private enum SettingsSection: Int, CaseIterable {
|
||||
case phone
|
||||
case accounts
|
||||
case proxy
|
||||
case stories
|
||||
case shortcuts
|
||||
case advanced
|
||||
case payment
|
||||
@ -785,6 +788,11 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
items[.stories]!.append(PeerInfoScreenDisclosureItem(id: 0, text: "My Stories", icon: PresentationResourcesSettings.stickers, action: {
|
||||
interaction.openSettings(.stories)
|
||||
}))
|
||||
|
||||
items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_SavedMessages, icon: PresentationResourcesSettings.savedMessages, action: {
|
||||
interaction.openSettings(.savedMessages)
|
||||
}))
|
||||
@ -7830,6 +7838,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil)
|
||||
case .proxy:
|
||||
self.controller?.push(proxySettingsController(context: self.context))
|
||||
case .stories:
|
||||
self.controller?.push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .saved))
|
||||
case .savedMessages:
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
@ -8671,6 +8681,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
} 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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user