Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-06-09 00:26:15 +04:00
commit 8335936796
14 changed files with 606 additions and 271 deletions

View File

@ -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() {

View File

@ -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))
}

View File

@ -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, randomId: Int64) -> Signal<StoryUploadResult, NoError> {
return _internal_uploadStory(account: self.account, media: media, text: text, entities: entities, pin: pin, privacy: privacy, period: period, randomId: randomId)
}

View File

@ -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,

View File

@ -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

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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() {

View File

@ -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))

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "StoryArchive.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -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()

View File

@ -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)
}))
@ -7849,6 +7857,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
@ -8690,6 +8700,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
}