[WIP] Stories

This commit is contained in:
Ali 2023-05-24 23:30:19 +04:00
parent a5e8db4f2b
commit 39963f3a6c
14 changed files with 826 additions and 549 deletions

View File

@ -1383,25 +1383,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
self.chatListDisplayNode.mainContainerNode.openStories = { [weak self] peerId in
guard let self, let storyListContext = self.storyListContext else {
guard let self else {
return
}
let _ = (StoryChatContent.stories(
context: self.context,
storyList: storyListContext,
focusItem: nil
)
let storyContent = StoryContentContextImpl(context: self.context, focusedPeerId: peerId)
let _ = (storyContent.state
|> filter { $0.slice != nil }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] initialContent in
|> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else {
return
}
let storyContainerScreen = StoryContainerScreen(
context: self.context,
initialFocusedId: AnyHashable(peerId),
initialContent: initialContent,
content: storyContent,
transitionIn: nil,
transitionOut: { _, _ in
return nil
@ -2438,11 +2435,116 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if let searchContentNode = self.searchContentNode, case .chatList(.root) = self.location {
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
componentView.storyPeerAction = { [weak self] peer in
guard let self, let peer else {
guard let self else {
return
}
let storyFocusContext = self.context.engine.messages.peerStoryFocusContext(id: peer.id, focusItemId: nil)
let storyContent = StoryContentContextImpl(context: self.context, focusedPeerId: peer?.id)
let _ = (storyContent.state
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] storyContentState in
guard let self else {
return
}
if let peer, peer.id == self.context.account.peerId, storyContentState.slice == nil {
var cameraTransitionIn: StoryCameraTransitionIn?
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
cameraTransitionIn = StoryCameraTransitionIn(
sourceView: transitionView,
sourceRect: transitionView.bounds,
sourceCornerRadius: transitionView.bounds.height * 0.5
)
}
}
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionOut: { [weak self] _ in
guard let self else {
return nil
}
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
return StoryCameraTransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height * 0.5
)
}
}
return nil
})
}
return
}
var transitionIn: StoryContainerScreen.TransitionIn?
if let peer, let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peer.id) {
transitionIn = StoryContainerScreen.TransitionIn(
sourceView: transitionView,
sourceRect: transitionView.bounds,
sourceCornerRadius: transitionView.bounds.height * 0.5
)
}
}
if let peer, peer.id == self.context.account.peerId {
if let stateValue = storyContent.stateValue {
let _ = stateValue
}
/*if initialFocusedId == AnyHashable(self.context.account.peerId), let firstItem = initialContent.first, firstItem.id == initialFocusedId && firstItem.items.isEmpty {
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionOut: { [weak self] _ in
guard let self else {
return nil
}
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
return StoryCameraTransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height * 0.5
)
}
}
return nil
})
}
}*/
}
let storyContainerScreen = StoryContainerScreen(
context: self.context,
content: storyContent,
transitionIn: transitionIn,
transitionOut: { [weak self] peerId, _ in
guard let self else {
return nil
}
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) {
return StoryContainerScreen.TransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height * 0.5,
destinationIsAvatar: true,
completed: {}
)
}
}
return nil
}
)
self.push(storyContainerScreen)
})
/*let storyFocusContext = self.context.engine.messages.peerStoryFocusContext(id: peer.id, focusItemId: nil)
let _ = (storyFocusContext.state
|> filter { state -> Bool in
@ -2550,7 +2652,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.push(storyContainerScreen)
})
}
})
})*/
/*let _ = (StoryChatContent.stories(
context: self.context,

View File

@ -68,6 +68,7 @@ public enum MediaPlayerStreaming {
case none
case conservative
case earlierStart
case story
public var enabled: Bool {
if case .none = self {
@ -83,6 +84,8 @@ public enum MediaPlayerStreaming {
return (1.0, 2.0, 3.0)
case .earlierStart:
return (1.0, 1.0, 2.0)
case .story:
return (0.25, 0.5, 2.0)
}
}
}

View File

@ -1266,6 +1266,11 @@ public final class Transaction {
self.postbox!.replaceAllStorySubscriptions(state: state, peerIds: peerIds)
}
public func getSubscriptionsStoriesState() -> CodableEntry? {
assert(!self.disposed)
return self.postbox!.getSubscriptionsStoriesState()
}
public func setSubscriptionsStoriesState(state: CodableEntry?) {
assert(!self.disposed)
self.postbox!.setSubscriptionsStoriesState(state: state)
@ -2177,6 +2182,10 @@ final class PostboxImpl {
return self.storyStatesTable.get(key: .local)
}
fileprivate func getSubscriptionsStoriesState() -> CodableEntry? {
return self.storyStatesTable.get(key: .subscriptions)
}
fileprivate func setSubscriptionsStoriesState(state: CodableEntry?) {
self.storyStatesTable.set(key: .subscriptions, value: state, events: &self.currentStoryStatesEvents)
}

View File

@ -4331,8 +4331,51 @@ func replayFinalState(
case let .UpdateStories(updateStories):
switch updateStories {
case let .userStories(_, userId, maxReadId, stories):
let _ = maxReadId
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
var updatedPeerEntries: [StoryItemsTableEntry] = transaction.getStoryItems(peerId: peerId)
for story in stories {
if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peerId, transaction: transaction) {
if let currentIndex = updatedPeerEntries.firstIndex(where: { $0.id == storedItem.id }) {
if case .item = storedItem {
if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries[currentIndex] = StoryItemsTableEntry(value: codedEntry, id: storedItem.id)
}
}
} else {
if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id))
}
}
} else {
if case let .storyItemDeleted(id) = story {
if let index = updatedPeerEntries.firstIndex(where: { $0.id == id }) {
updatedPeerEntries.remove(at: index)
}
}
}
}
var subscriptionsOpaqueState: String?
if let state = transaction.getSubscriptionsStoriesState()?.get(Stories.SubscriptionsState.self) {
subscriptionsOpaqueState = state.opaqueState
}
var appliedMaxReadId = maxReadId
if let currentState = transaction.getPeerStoryState(peerId: peerId)?.get(Stories.PeerState.self) {
if let appliedMaxReadIdValue = appliedMaxReadId {
appliedMaxReadId = max(appliedMaxReadIdValue, currentState.maxReadId)
} else {
appliedMaxReadId = currentState.maxReadId
}
}
transaction.setStoryItems(peerId: peerId, items: updatedPeerEntries)
transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState(
subscriptionsOpaqueState: subscriptionsOpaqueState,
maxReadId: appliedMaxReadId ?? 0
)))
for storyItem in stories {
if let parsedItem = _internal_parseApiStoryItem(transaction: transaction, peerId: peerId, apiStory: storyItem) {
storyUpdates.append(InternalStoryUpdate.added(peerId: peerId, item: parsedItem))

View File

@ -432,15 +432,20 @@ public final class EngineStorySubscriptions: Equatable {
}
}
public let accountItem: Item?
public let items: [Item]
public let hasMoreToken: String?
public init(items: [Item], hasMoreToken: String?) {
public init(accountItem: Item?, items: [Item], hasMoreToken: String?) {
self.accountItem = accountItem
self.items = items
self.hasMoreToken = hasMoreToken
}
public static func ==(lhs: EngineStorySubscriptions, rhs: EngineStorySubscriptions) -> Bool {
if lhs.accountItem != rhs.accountItem {
return false
}
if lhs.items != rhs.items {
return false
}

View File

@ -599,13 +599,13 @@ public extension TelegramEngine {
])
|> mapToSignal { views -> Signal<EngineStorySubscriptions, NoError> in
guard let basicPeerView = views.views[basicPeerKey] as? BasicPeerView, let accountPeer = basicPeerView.peer else {
return .single(EngineStorySubscriptions(items: [], hasMoreToken: nil))
return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil))
}
guard let storySubscriptionsView = views.views[PostboxViewKey.storySubscriptions] as? StorySubscriptionsView else {
return .single(EngineStorySubscriptions(items: [], hasMoreToken: nil))
return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil))
}
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else {
return .single(EngineStorySubscriptions(items: [], hasMoreToken: nil))
return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil))
}
var additionalDataKeys: [PostboxViewKey] = []
@ -622,8 +622,6 @@ public extension TelegramEngine {
return self.account.postbox.combinedView(keys: additionalDataKeys)
|> map { views -> EngineStorySubscriptions in
let _ = accountPeer
let _ = storySubscriptionsView
let _ = storiesStateView
var hasMoreToken: String?
if let subscriptionsState = storiesStateView.value?.get(Stories.SubscriptionsState.self) {
@ -636,6 +634,13 @@ public extension TelegramEngine {
hasMoreToken = ""
}
var accountItem: EngineStorySubscriptions.Item = EngineStorySubscriptions.Item(
peer: EnginePeer(accountPeer),
hasUnseen: false,
storyCount: 0,
lastTimestamp: 0
)
var items: [EngineStorySubscriptions.Item] = []
for peerId in storySubscriptionsView.peerIds {
guard let peerView = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView else {
@ -660,19 +665,25 @@ public extension TelegramEngine {
hasUnseen = peerState.maxReadId < lastEntry.id
}
items.append(EngineStorySubscriptions.Item(
let item = EngineStorySubscriptions.Item(
peer: EnginePeer(peer),
hasUnseen: hasUnseen,
storyCount: itemsView.items.count,
lastTimestamp: lastEntry.timestamp
))
)
if peerId == accountPeer.id {
accountItem = item
} else {
items.append(item)
}
}
items.sort(by: { lhs, rhs in
return lhs.lastTimestamp > rhs.lastTimestamp
})
return EngineStorySubscriptions(items: items, hasMoreToken: hasMoreToken)
return EngineStorySubscriptions(accountItem: accountItem, items: items, hasMoreToken: hasMoreToken)
}
}
}

View File

@ -889,7 +889,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
}
strongSelf.chatControllerInteraction.toggleMessagesSelection([item.message.id], toggledValue)*/
} else {
let _ = (StoryChatContent.stories(
/*let _ = (StoryChatContent.stories(
context: self.context,
storyList: self.listSource,
focusItem: item.story.id
@ -956,7 +956,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
}
)
navigationController.pushViewController(storyContainerScreen)
})
})*/
//TODO:open
//let _ = strongSelf.chatControllerInteraction.openMessage(item.message, .default)
}

View File

@ -33,21 +33,18 @@ private final class StoryContainerScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let initialFocusedId: AnyHashable?
let initialContent: [StoryContentItemSlice]
let content: StoryContentContext
let transitionIn: StoryContainerScreen.TransitionIn?
let transitionOut: (EnginePeer.Id, AnyHashable) -> StoryContainerScreen.TransitionOut?
init(
context: AccountContext,
initialFocusedId: AnyHashable?,
initialContent: [StoryContentItemSlice],
content: StoryContentContext,
transitionIn: StoryContainerScreen.TransitionIn?,
transitionOut: @escaping (EnginePeer.Id, AnyHashable) -> StoryContainerScreen.TransitionOut?
) {
self.context = context
self.initialFocusedId = initialFocusedId
self.initialContent = initialContent
self.content = content
self.transitionIn = transitionIn
self.transitionOut = transitionOut
}
@ -56,6 +53,9 @@ private final class StoryContainerScreenComponent: Component {
if lhs.context !== rhs.context {
return false
}
if lhs.content !== rhs.content {
return false
}
return true
}
@ -117,9 +117,9 @@ private final class StoryContainerScreenComponent: Component {
private let backgroundLayer: SimpleLayer
private var focusedItemSet: AnyHashable?
private var itemSets: [StoryContentItemSlice] = []
private var visibleItemSetViews: [AnyHashable: ItemSetView] = [:]
private var contentUpdatedDisposable: Disposable?
private var visibleItemSetViews: [EnginePeer.Id: ItemSetView] = [:]
private var itemSetPanState: ItemSetPanState?
private var dismissPanState: ItemSetPanState?
@ -137,7 +137,7 @@ private final class StoryContainerScreenComponent: Component {
self.layer.addSublayer(self.backgroundLayer)
let horizontalPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
guard let self, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
guard let self, let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
return []
}
if !itemSetComponentView.isPointInsideContentArea(point: self.convert(point, to: itemSetComponentView)) {
@ -148,7 +148,7 @@ private final class StoryContainerScreenComponent: Component {
self.addGestureRecognizer(horizontalPanRecognizer)
let verticalPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.dismissPanGesture(_:)), allowedDirections: { [weak self] point in
guard let self, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
guard let self, let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
return []
}
if !itemSetComponentView.isPointInsideContentArea(point: self.convert(point, to: itemSetComponentView)) {
@ -169,11 +169,12 @@ private final class StoryContainerScreenComponent: Component {
}
deinit {
self.contentUpdatedDisposable?.dispose()
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
guard let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
return true
guard let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
return false
}
if !itemSetComponentView.isPointInsideContentArea(point: touch.location(in: itemSetComponentView)) {
@ -183,85 +184,102 @@ private final class StoryContainerScreenComponent: Component {
return true
}
private func beginHorizontalPan() {
self.layer.removeAnimation(forKey: "panState")
if let itemSetPanState = self.itemSetPanState, !itemSetPanState.didBegin {
self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true)
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
} else {
self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true)
self.state?.updated(transition: .immediate)
}
}
private func updateHorizontalPan(translation: CGPoint) {
var translation = translation
if var itemSetPanState = self.itemSetPanState, self.bounds.width > 0.0, let component = self.component, let stateValue = component.content.stateValue, let _ = stateValue.slice {
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
let bandedOffset = offset - bandingStart
let range: CGFloat = 600.0
let coefficient: CGFloat = 0.4
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
}
if translation.x > 0.0 && stateValue.previousSlice == nil {
translation.x = rubberBandingOffset(offset: translation.x, bandingStart: 0.0)
} else if translation.x < 0.0 && stateValue.nextSlice == nil {
translation.x = -rubberBandingOffset(offset: -translation.x, bandingStart: 0.0)
}
var fraction = translation.x / self.bounds.width
fraction = -max(-1.0, min(1.0, fraction))
itemSetPanState.fraction = fraction
self.itemSetPanState = itemSetPanState
self.state?.updated(transition: .immediate)
}
}
private func commitHorizontalPan(velocity: CGPoint) {
if var itemSetPanState = self.itemSetPanState {
if let component = self.component, let stateValue = component.content.stateValue, let _ = stateValue.slice {
var direction: StoryContentContextNavigation.Direction?
if abs(velocity.x) > 10.0 {
if velocity.x < 0.0 {
if stateValue.nextSlice != nil {
direction = .next
}
} else {
if stateValue.previousSlice != nil {
direction = .previous
}
}
}
if let direction {
component.content.navigate(navigation: .peer(direction))
if case .previous = direction {
itemSetPanState.fraction = 1.0 + itemSetPanState.fraction
} else {
itemSetPanState.fraction = itemSetPanState.fraction - 1.0
}
self.itemSetPanState = itemSetPanState
self.state?.updated(transition: .immediate)
}
}
itemSetPanState.fraction = 0.0
self.itemSetPanState = itemSetPanState
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
self.state?.updated(transition: transition)
transition.attachAnimation(view: self, id: "panState", completion: { [weak self] completed in
guard let self, completed else {
return
}
self.itemSetPanState = nil
self.state?.updated(transition: .immediate)
if let component = self.component {
component.content.resetSideStates()
}
})
}
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
self.layer.removeAnimation(forKey: "panState")
if let itemSetPanState = self.itemSetPanState, !itemSetPanState.didBegin {
self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true)
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
} else {
self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true)
self.state?.updated(transition: .immediate)
}
self.beginHorizontalPan()
case .changed:
if var itemSetPanState = self.itemSetPanState, self.bounds.width > 0.0, let focusedItemSet = self.focusedItemSet, let focusedIndex = self.itemSets.firstIndex(where: { $0.id == focusedItemSet }) {
var translation = recognizer.translation(in: self)
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
let bandedOffset = offset - bandingStart
let range: CGFloat = 600.0
let coefficient: CGFloat = 0.4
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
}
if translation.x > 0.0 && focusedIndex == 0 {
translation.x = rubberBandingOffset(offset: translation.x, bandingStart: 0.0)
} else if translation.x < 0.0 && focusedIndex == self.itemSets.count - 1 {
translation.x = -rubberBandingOffset(offset: -translation.x, bandingStart: 0.0)
}
var fraction = translation.x / self.bounds.width
fraction = -max(-1.0, min(1.0, fraction))
itemSetPanState.fraction = fraction
self.itemSetPanState = itemSetPanState
self.state?.updated(transition: .immediate)
}
self.updateHorizontalPan(translation: recognizer.translation(in: self))
case .cancelled, .ended:
if var itemSetPanState = self.itemSetPanState {
if let focusedItemSet = self.focusedItemSet, let focusedIndex = self.itemSets.firstIndex(where: { $0.id == focusedItemSet }) {
let velocity = recognizer.velocity(in: self)
var switchToIndex = focusedIndex
if abs(velocity.x) > 10.0 {
if velocity.x < 0.0 {
switchToIndex += 1
} else {
switchToIndex -= 1
}
}
switchToIndex = max(0, min(switchToIndex, self.itemSets.count - 1))
if switchToIndex != focusedIndex {
self.focusedItemSet = self.itemSets[switchToIndex].id
if switchToIndex < focusedIndex {
itemSetPanState.fraction = 1.0 + itemSetPanState.fraction
} else {
itemSetPanState.fraction = itemSetPanState.fraction - 1.0
}
self.itemSetPanState = itemSetPanState
self.state?.updated(transition: .immediate)
}
}
itemSetPanState.fraction = 0.0
self.itemSetPanState = itemSetPanState
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
self.state?.updated(transition: transition)
transition.attachAnimation(view: self, id: "panState", completion: { [weak self] completed in
guard let self, completed else {
return
}
self.itemSetPanState = nil
self.state?.updated(transition: .immediate)
})
}
self.commitHorizontalPan(velocity: recognizer.velocity(in: self))
default:
break
}
@ -313,9 +331,12 @@ private final class StoryContainerScreenComponent: Component {
if !subview.isUserInteractionEnabled || subview.isHidden || subview.alpha == 0.0 {
continue
}
if subview is ItemSetView {
if let result = subview.hitTest(point, with: event) {
return result
if self.itemSetPanState == nil {
if let result = subview.hitTest(point, with: event) {
return result
}
}
} else {
if let result = subview.hitTest(self.convert(point, to: subview), with: event) {
@ -331,7 +352,7 @@ private final class StoryContainerScreenComponent: Component {
if let transitionIn = self.component?.transitionIn, transitionIn.sourceView != nil {
self.backgroundLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.28, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
if let transitionIn = self.component?.transitionIn, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet] {
if let transitionIn = self.component?.transitionIn, let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] {
if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
itemSetComponentView.animateIn(transitionIn: transitionIn)
}
@ -348,7 +369,7 @@ private final class StoryContainerScreenComponent: Component {
self.isAnimatingOut = true
self.state?.updated(transition: .immediate)
if let component = self.component, let focusedItemSet = self.focusedItemSet, let peerId = focusedItemSet.base as? EnginePeer.Id, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View, let focusedItemId = itemSetComponentView.focusedItemId, let transitionOut = component.transitionOut(peerId, focusedItemId) {
if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View, let transitionOut = component.transitionOut(slice.peer.id, slice.item.id) {
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
transition.setAlpha(layer: self.backgroundLayer, alpha: 0.0)
@ -403,23 +424,27 @@ private final class StoryContainerScreenComponent: Component {
return availableSize
}
let isFirstTime = self.component == nil
let environment = environment[ViewControllerComponentContainer.Environment.self].value
self.environment = environment
if self.component?.content !== component.content {
self.contentUpdatedDisposable?.dispose()
var update = false
self.contentUpdatedDisposable = (component.content.updated
|> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else {
return
}
if update {
self.state?.updated(transition: .immediate)
}
})
update = true
}
self.component = component
self.state = state
if isFirstTime {
if let initialFocusedId = component.initialFocusedId, component.initialContent.contains(where: { $0.id == initialFocusedId }) {
self.focusedItemSet = initialFocusedId
} else {
self.focusedItemSet = component.initialContent.first?.id
}
self.itemSets = component.initialContent
}
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize))
var isProgressPaused = false
@ -447,17 +472,33 @@ private final class StoryContainerScreenComponent: Component {
var contentDerivedBottomInset: CGFloat = environment.safeInsets.bottom
var validIds: [AnyHashable] = []
if let focusedItemSet = self.focusedItemSet, let focusedIndex = self.itemSets.firstIndex(where: { $0.id == focusedItemSet }) {
for i in max(0, focusedIndex - 1) ... min(focusedIndex + 1, self.itemSets.count - 1) {
var currentSlices: [StoryContentContextState.FocusedSlice] = []
var focusedIndex: Int?
if let component = self.component, let stateValue = component.content.stateValue {
if let previousSlice = stateValue.previousSlice {
currentSlices.append(previousSlice)
}
if let slice = stateValue.slice {
focusedIndex = currentSlices.count
currentSlices.append(slice)
}
if let nextSlice = stateValue.nextSlice {
currentSlices.append(nextSlice)
}
}
if !currentSlices.isEmpty, let focusedIndex {
for i in max(0, focusedIndex - 1) ... min(focusedIndex + 1, currentSlices.count - 1) {
var isItemVisible = false
if i == focusedIndex {
isItemVisible = true
}
let itemSet = self.itemSets[i]
let slice = currentSlices[i]
if let itemSetPanState = self.itemSetPanState {
if self.visibleItemSetViews[itemSet.id] != nil {
if self.visibleItemSetViews[slice.peer.id] != nil {
isItemVisible = true
}
if itemSetPanState.fraction < 0.0 && i == focusedIndex - 1 {
@ -469,23 +510,24 @@ private final class StoryContainerScreenComponent: Component {
}
if isItemVisible {
validIds.append(itemSet.id)
validIds.append(slice.peer.id)
let itemSetView: ItemSetView
var itemSetTransition = transition
if let current = self.visibleItemSetViews[itemSet.id] {
if let current = self.visibleItemSetViews[slice.peer.id] {
itemSetView = current
} else {
itemSetTransition = .immediate
itemSetView = ItemSetView()
self.visibleItemSetViews[itemSet.id] = itemSetView
self.visibleItemSetViews[slice.peer.id] = itemSetView
}
let _ = itemSetView.view.update(
transition: itemSetTransition,
component: AnyComponent(StoryItemSetContainerComponent(
context: component.context,
externalState: itemSetView.externalState,
initialItemSlice: itemSet,
slice: slice,
theme: environment.theme,
strings: environment.strings,
containerInsets: UIEdgeInsets(top: environment.statusBarHeight + 12.0, left: 0.0, bottom: environment.inputHeight, right: 0.0),
@ -509,52 +551,42 @@ private final class StoryContainerScreenComponent: Component {
}
environment.controller()?.dismiss()
},
navigateToItemSet: { [weak self] direction in
guard let self, let environment = self.environment else {
navigate: { [weak self] direction in
guard let self, let component = self.component, let environment = self.environment else {
return
}
if let focusedItemSet = self.focusedItemSet, let focusedIndex = self.itemSets.firstIndex(where: { $0.id == focusedItemSet }) {
var switchToIndex = focusedIndex
switch direction {
case .previous:
switchToIndex -= 1
case .next:
switchToIndex += 1
}
switchToIndex = max(0, min(switchToIndex, self.itemSets.count - 1))
if switchToIndex != focusedIndex {
var itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true)
self.focusedItemSet = self.itemSets[switchToIndex].id
if switchToIndex < focusedIndex {
itemSetPanState.fraction = 1.0 + itemSetPanState.fraction
if let stateValue = component.content.stateValue, let slice = stateValue.slice {
if case .next = direction, slice.nextItemId == nil {
if stateValue.nextSlice == nil {
environment.controller()?.dismiss()
} else {
itemSetPanState.fraction = itemSetPanState.fraction - 1.0
self.beginHorizontalPan()
self.updateHorizontalPan(translation: CGPoint())
self.commitHorizontalPan(velocity: CGPoint(x: -100.0, y: 0.0))
}
self.itemSetPanState = itemSetPanState
self.state?.updated(transition: .immediate)
itemSetPanState.fraction = 0.0
self.itemSetPanState = itemSetPanState
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
self.state?.updated(transition: transition)
transition.attachAnimation(view: self, id: "panState", completion: { [weak self] completed in
guard let self, completed else {
return
} else if case .previous = direction, slice.previousItemId == nil {
if stateValue.previousSlice == nil {
if let itemSetView = self.visibleItemSetViews[slice.peer.id] {
if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
componentView.rewindCurrentItem()
}
}
self.itemSetPanState = nil
self.state?.updated(transition: .immediate)
})
} else if switchToIndex == self.itemSets.count - 1 {
environment.controller()?.dismiss()
} else {
self.beginHorizontalPan()
self.updateHorizontalPan(translation: CGPoint())
self.commitHorizontalPan(velocity: CGPoint(x: 100.0, y: 0.0))
}
} else {
let mappedDirection: StoryContentContextNavigation.Direction
switch direction {
case .previous:
mappedDirection = .previous
case .next:
mappedDirection = .next
}
component.content.navigate(navigation: .item(mappedDirection))
}
} else {
environment.controller()?.dismiss()
}
},
controller: { [weak self] in
@ -695,7 +727,7 @@ private final class StoryContainerScreenComponent: Component {
}
}
}
var removedIds: [AnyHashable] = []
var removedIds: [EnginePeer.Id] = []
for (id, itemSetView) in self.visibleItemSetViews {
if !validIds.contains(id) {
removedIds.append(id)
@ -779,8 +811,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
public init(
context: AccountContext,
initialFocusedId: AnyHashable?,
initialContent: [StoryContentItemSlice],
content: StoryContentContext,
transitionIn: TransitionIn?,
transitionOut: @escaping (EnginePeer.Id, AnyHashable) -> TransitionOut?
) {
@ -788,8 +819,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
super.init(context: context, component: StoryContainerScreenComponent(
context: context,
initialFocusedId: initialFocusedId,
initialContent: initialContent,
content: content,
transitionIn: transitionIn,
transitionOut: transitionOut
), navigationBarAppearance: .none, theme: .dark)

View File

@ -14,6 +14,9 @@ public final class StoryContentItem {
open class View: UIView {
open func setIsProgressPaused(_ isProgressPaused: Bool) {
}
open func rewind() {
}
}
public final class Environment: Equatable {
@ -42,7 +45,7 @@ public final class StoryContentItem {
public let centerInfoComponent: AnyComponent<Empty>?
public let rightInfoComponent: AnyComponent<Empty>?
public let peerId: EnginePeer.Id?
public let storyItem: StoryListContext.Item?
public let storyItem: StoryListContext.Item
public let preload: Signal<Never, NoError>?
public let delete: (() -> Void)?
public let markAsSeen: (() -> Void)?
@ -56,7 +59,7 @@ public final class StoryContentItem {
centerInfoComponent: AnyComponent<Empty>?,
rightInfoComponent: AnyComponent<Empty>?,
peerId: EnginePeer.Id?,
storyItem: StoryListContext.Item?,
storyItem: StoryListContext.Item,
preload: Signal<Never, NoError>?,
delete: (() -> Void)?,
markAsSeen: (() -> Void)?,
@ -105,3 +108,79 @@ public final class StoryContentItemSlice {
self.update = update
}
}
public final class StoryContentContextState {
public final class FocusedSlice: Equatable {
public let peer: EnginePeer
public let item: StoryContentItem
public let totalCount: Int
public let previousItemId: Int32?
public let nextItemId: Int32?
public init(
peer: EnginePeer,
item: StoryContentItem,
totalCount: Int,
previousItemId: Int32?,
nextItemId: Int32?
) {
self.peer = peer
self.item = item
self.totalCount = totalCount
self.previousItemId = previousItemId
self.nextItemId = nextItemId
}
public static func ==(lhs: FocusedSlice, rhs: FocusedSlice) -> Bool {
if lhs.peer != rhs.peer {
return false
}
if lhs.item.id != rhs.item.id {
return false
}
if lhs.totalCount != rhs.totalCount {
return false
}
if lhs.previousItemId != rhs.previousItemId {
return false
}
if lhs.nextItemId != rhs.nextItemId {
return false
}
return true
}
}
public let slice: FocusedSlice?
public let previousSlice: FocusedSlice?
public let nextSlice: FocusedSlice?
public init(
slice: FocusedSlice?,
previousSlice: FocusedSlice?,
nextSlice: FocusedSlice?
) {
self.slice = slice
self.previousSlice = previousSlice
self.nextSlice = nextSlice
}
}
public enum StoryContentContextNavigation {
public enum Direction {
case previous
case next
}
case item(Direction)
case peer(Direction)
}
public protocol StoryContentContext: AnyObject {
var stateValue: StoryContentContextState? { get }
var state: Signal<StoryContentContextState, NoError> { get }
var updated: Signal<Void, NoError> { get }
func resetSideStates()
func navigate(navigation: StoryContentContextNavigation)
}

View File

@ -32,7 +32,7 @@ public final class StoryItemSetContainerComponent: Component {
public let context: AccountContext
public let externalState: ExternalState
public let initialItemSlice: StoryContentItemSlice
public let slice: StoryContentContextState.FocusedSlice
public let theme: PresentationTheme
public let strings: PresentationStrings
public let containerInsets: UIEdgeInsets
@ -42,13 +42,13 @@ public final class StoryItemSetContainerComponent: Component {
public let hideUI: Bool
public let presentController: (ViewController) -> Void
public let close: () -> Void
public let navigateToItemSet: (NavigationDirection) -> Void
public let navigate: (NavigationDirection) -> Void
public let controller: () -> ViewController?
public init(
context: AccountContext,
externalState: ExternalState,
initialItemSlice: StoryContentItemSlice,
slice: StoryContentContextState.FocusedSlice,
theme: PresentationTheme,
strings: PresentationStrings,
containerInsets: UIEdgeInsets,
@ -58,12 +58,12 @@ public final class StoryItemSetContainerComponent: Component {
hideUI: Bool,
presentController: @escaping (ViewController) -> Void,
close: @escaping () -> Void,
navigateToItemSet: @escaping (NavigationDirection) -> Void,
navigate: @escaping (NavigationDirection) -> Void,
controller: @escaping () -> ViewController?
) {
self.context = context
self.externalState = externalState
self.initialItemSlice = initialItemSlice
self.slice = slice
self.theme = theme
self.strings = strings
self.containerInsets = containerInsets
@ -73,7 +73,7 @@ public final class StoryItemSetContainerComponent: Component {
self.hideUI = hideUI
self.presentController = presentController
self.close = close
self.navigateToItemSet = navigateToItemSet
self.navigate = navigate
self.controller = controller
}
@ -81,7 +81,7 @@ public final class StoryItemSetContainerComponent: Component {
if lhs.context !== rhs.context {
return false
}
if lhs.initialItemSlice !== rhs.initialItemSlice {
if lhs.slice != rhs.slice {
return false
}
if lhs.theme !== rhs.theme {
@ -171,10 +171,6 @@ public final class StoryItemSetContainerComponent: Component {
var itemLayout: ItemLayout?
var ignoreScrolling: Bool = false
var focusedItemId: AnyHashable?
var currentSlice: StoryContentItemSlice?
var currentSliceDisposable: Disposable?
var visibleItems: [AnyHashable: VisibleItem] = [:]
var preloadContexts: [AnyHashable: Disposable] = [:]
@ -330,7 +326,6 @@ public final class StoryItemSetContainerComponent: Component {
}
deinit {
self.currentSliceDisposable?.dispose()
self.audioRecorderDisposable?.dispose()
self.audioRecorderStatusDisposable?.dispose()
self.audioRecorderStatusDisposable?.dispose()
@ -350,6 +345,18 @@ public final class StoryItemSetContainerComponent: Component {
return false
}
func rewindCurrentItem() {
guard let component = self.component else {
return
}
guard let visibleItem = self.visibleItems[component.slice.item.id] else {
return
}
if let itemView = visibleItem.view.view as? StoryContentItem.View {
itemView.rewind()
}
}
@objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if otherGestureRecognizer is UIPanGestureRecognizer {
return true
@ -358,7 +365,7 @@ public final class StoryItemSetContainerComponent: Component {
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state, let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let currentIndex = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }), let itemLayout = self.itemLayout {
if case .ended = recognizer.state, let component = self.component, let itemLayout = self.itemLayout {
if hasFirstResponder(self) {
self.displayReactions = false
self.endEditing(true)
@ -368,70 +375,15 @@ public final class StoryItemSetContainerComponent: Component {
} else {
let point = recognizer.location(in: self)
var nextIndex: Int
var direction: NavigationDirection?
if point.x < itemLayout.size.width * 0.25 {
nextIndex = currentIndex - 1
direction = .previous
} else {
nextIndex = currentIndex + 1
direction = .next
}
if nextIndex < 0, let previousId = currentSlice.previousItemId {
self.currentSliceDisposable?.dispose()
self.currentSliceDisposable = (currentSlice.update(
currentSlice,
previousId
)
|> deliverOnMainQueue).start(next: { [weak self] contentSlice in
guard let self else {
return
}
self.currentSlice = contentSlice
self.focusedItemId = previousId
self.state?.updated(transition: .immediate)
})
} else if nextIndex >= currentSlice.items.count - 1, let nextId = currentSlice.nextItemId {
self.currentSliceDisposable?.dispose()
self.currentSliceDisposable = (currentSlice.update(
currentSlice,
nextId
)
|> deliverOnMainQueue).start(next: { [weak self] contentSlice in
guard let self else {
return
}
self.currentSlice = contentSlice
self.focusedItemId = nextId
self.state?.updated(transition: .immediate)
})
} else {
nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1))
if nextIndex != currentIndex {
let focusedItemId = currentSlice.items[nextIndex].id
self.focusedItemId = focusedItemId
currentSlice.items[nextIndex].markAsSeen?()
self.state?.updated(transition: .immediate)
self.currentSliceDisposable?.dispose()
self.currentSliceDisposable = (currentSlice.update(
currentSlice,
focusedItemId
)
|> deliverOnMainQueue).start(next: { [weak self] contentSlice in
guard let self else {
return
}
self.currentSlice = contentSlice
self.state?.updated(transition: .immediate)
})
} else {
if point.x < itemLayout.size.width * 0.25 {
self.component?.navigateToItemSet(.previous)
} else {
self.component?.navigateToItemSet(.next)
}
}
if let direction {
component.navigate(direction)
}
}
}
@ -463,83 +415,57 @@ public final class StoryItemSetContainerComponent: Component {
}
var validIds: [AnyHashable] = []
if let focusedItemId = self.focusedItemId, let focusedItem = self.currentSlice?.items.first(where: { $0.id == focusedItemId }) {
validIds.append(focusedItemId)
let focusedItem = component.slice.item
validIds.append(focusedItem.id)
var itemTransition = transition
let visibleItem: VisibleItem
if let current = self.visibleItems[focusedItemId] {
visibleItem = current
} else {
itemTransition = .immediate
visibleItem = VisibleItem()
self.visibleItems[focusedItemId] = visibleItem
}
let _ = visibleItem.view.update(
transition: itemTransition,
component: focusedItem.component,
environment: {
StoryContentItem.Environment(
externalState: visibleItem.externalState,
presentationProgressUpdated: { [weak self, weak visibleItem] progress in
guard let self = self else {
return
}
guard let visibleItem else {
return
}
visibleItem.currentProgress = progress
if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View {
navigationStripView.updateCurrentItemProgress(value: progress, transition: .immediate)
}
if progress >= 1.0 && !visibleItem.requestedNext {
visibleItem.requestedNext = true
if let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let currentIndex = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }) {
var nextIndex = currentIndex + 1
nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1))
if nextIndex != currentIndex {
let focusedItemId = currentSlice.items[nextIndex].id
self.focusedItemId = focusedItemId
currentSlice.items[nextIndex].markAsSeen?()
self.state?.updated(transition: .immediate)
self.currentSliceDisposable?.dispose()
self.currentSliceDisposable = (currentSlice.update(
currentSlice,
focusedItemId
)
|> deliverOnMainQueue).start(next: { [weak self] contentSlice in
guard let self else {
return
}
self.currentSlice = contentSlice
self.state?.updated(transition: .immediate)
})
} else {
self.component?.navigateToItemSet(.next)
}
}
}
var itemTransition = transition
let visibleItem: VisibleItem
if let current = self.visibleItems[focusedItem.id] {
visibleItem = current
} else {
itemTransition = .immediate
visibleItem = VisibleItem()
self.visibleItems[focusedItem.id] = visibleItem
}
let _ = visibleItem.view.update(
transition: itemTransition,
component: focusedItem.component,
environment: {
StoryContentItem.Environment(
externalState: visibleItem.externalState,
presentationProgressUpdated: { [weak self, weak visibleItem] progress in
guard let self = self, let component = self.component else {
return
}
)
},
containerSize: itemLayout.size
)
if let view = visibleItem.view.view {
if view.superview == nil {
view.isUserInteractionEnabled = false
self.contentContainerView.insertSubview(view, at: 0)
}
itemTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: itemLayout.size))
if let view = view as? StoryContentItem.View {
view.setIsProgressPaused(self.inputPanelExternalState.isEditing || component.isProgressPaused || self.displayReactions || self.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil)
}
guard let visibleItem else {
return
}
visibleItem.currentProgress = progress
if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View {
navigationStripView.updateCurrentItemProgress(value: progress, transition: .immediate)
}
if progress >= 1.0 && !visibleItem.requestedNext {
visibleItem.requestedNext = true
component.navigate(.next)
}
}
)
},
containerSize: itemLayout.size
)
if let view = visibleItem.view.view {
if view.superview == nil {
view.isUserInteractionEnabled = false
self.contentContainerView.insertSubview(view, at: 0)
}
itemTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: itemLayout.size))
if let view = view as? StoryContentItem.View {
view.setIsProgressPaused(self.inputPanelExternalState.isEditing || component.isProgressPaused || self.displayReactions || self.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil)
}
}
@ -617,7 +543,7 @@ public final class StoryItemSetContainerComponent: Component {
duration: 0.3
)
if let focusedItemId = self.focusedItemId, let visibleItemView = self.visibleItems[focusedItemId]?.view.view {
if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view {
let innerScale = innerSourceLocalFrame.width / visibleItemView.bounds.width
let innerFromFrame = CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: CGSize(width: innerSourceLocalFrame.width, height: visibleItemView.bounds.height * innerScale))
@ -688,7 +614,7 @@ public final class StoryItemSetContainerComponent: Component {
removeOnCompletion: false
)
if let focusedItemId = self.focusedItemId, let visibleItemView = self.visibleItems[focusedItemId]?.view.view {
if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view {
let innerScale = innerSourceLocalFrame.width / visibleItemView.bounds.width
let innerFromFrame = CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: CGSize(width: innerSourceLocalFrame.width, height: visibleItemView.bounds.height * innerScale))
@ -711,24 +637,6 @@ public final class StoryItemSetContainerComponent: Component {
let isFirstTime = self.component == nil
if self.component == nil {
self.focusedItemId = component.initialItemSlice.focusedItemId ?? component.initialItemSlice.items.first?.id
self.currentSlice = component.initialItemSlice
self.currentSliceDisposable?.dispose()
if let focusedItemId = self.focusedItemId {
self.currentSliceDisposable = (component.initialItemSlice.update(
component.initialItemSlice,
focusedItemId
)
|> deliverOnMainQueue).start(next: { [weak self] contentSlice in
guard let self else {
return
}
self.currentSlice = contentSlice
self.state?.updated(transition: .immediate)
})
}
let _ = (allowedStoryReactions(context: component.context)
|> deliverOnMainQueue).start(next: { [weak self] reactionItems in
guard let self, let component = self.component else {
@ -790,18 +698,6 @@ public final class StoryItemSetContainerComponent: Component {
self.contentDimLayer.backgroundColor = UIColor(white: 0.0, alpha: 0.3).cgColor
}
if let focusedItemId = self.focusedItemId {
if let currentSlice = self.currentSlice {
if !currentSlice.items.contains(where: { $0.id == focusedItemId }) {
self.focusedItemId = currentSlice.items.first?.id
currentSlice.items.first?.markAsSeen?()
}
} else {
self.focusedItemId = nil
}
}
//self.updatePreloads()
self.component = component
@ -905,9 +801,7 @@ public final class StoryItemSetContainerComponent: Component {
)
var currentItem: StoryContentItem?
if let focusedItemId = self.focusedItemId, let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == focusedItemId }) {
currentItem = item
}
currentItem = component.slice.item
let footerPanelSize = self.footerPanel.update(
transition: transition,
@ -915,7 +809,8 @@ public final class StoryItemSetContainerComponent: Component {
context: component.context,
storyItem: currentItem?.storyItem,
deleteAction: { [weak self] in
guard let self, let component = self.component, let focusedItemId = self.focusedItemId else {
let _ = self
/*guard let self, let component = self.component, let focusedItemId = self.focusedItemId else {
return
}
@ -969,14 +864,16 @@ public final class StoryItemSetContainerComponent: Component {
self.actionSheet = actionSheet
self.updateIsProgressPaused()
component.presentController(actionSheet)
component.presentController(actionSheet)*/
},
moreAction: { [weak self] sourceView, gesture in
guard let self, let component = self.component, let controller = component.controller() else {
return
}
var items: [ContextMenuItem] = []
let _ = controller
/*var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: "Who can see", textLayout: .secondLineWithValue("Everyone"), icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor)
@ -1035,7 +932,7 @@ public final class StoryItemSetContainerComponent: Component {
}
self.contextController = contextController
self.updateIsProgressPaused()
controller.present(contextController, in: .window(.root))
controller.present(contextController, in: .window(.root))*/
}
)),
environment: {},
@ -1071,15 +968,16 @@ public final class StoryItemSetContainerComponent: Component {
transition.setAlpha(view: self.closeButton, alpha: component.hideUI ? 0.0 : 1.0)
}
var focusedItem: StoryContentItem?
if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) {
let focusedItem: StoryContentItem? = component.slice.item
let _ = focusedItem
/*if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) {
focusedItem = item
}
}*/
var currentRightInfoItem: InfoItem?
if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) {
if let rightInfoComponent = item.rightInfoComponent {
if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == item.rightInfoComponent {
if let focusedItem {
if let rightInfoComponent = focusedItem.rightInfoComponent {
if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == focusedItem.rightInfoComponent {
currentRightInfoItem = rightInfoItem
} else {
currentRightInfoItem = InfoItem(component: rightInfoComponent)
@ -1098,9 +996,9 @@ public final class StoryItemSetContainerComponent: Component {
}
var currentCenterInfoItem: InfoItem?
if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) {
if let centerInfoComponent = item.centerInfoComponent {
if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == item.centerInfoComponent {
if let focusedItem {
if let centerInfoComponent = focusedItem.centerInfoComponent {
if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == focusedItem.centerInfoComponent {
currentCenterInfoItem = centerInfoItem
} else {
currentCenterInfoItem = InfoItem(component: centerInfoComponent)
@ -1395,17 +1293,17 @@ public final class StoryItemSetContainerComponent: Component {
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
if let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let visibleItem = self.visibleItems[focusedItemId] {
if let focusedItem, let visibleItem = self.visibleItems[focusedItem.storyItem.id] {
let navigationStripSideInset: CGFloat = 8.0
let navigationStripTopInset: CGFloat = 8.0
let index = currentSlice.items.first(where: { $0.id == self.focusedItemId })?.position ?? 0
let index = focusedItem.position
let _ = self.navigationStrip.update(
transition: transition,
component: AnyComponent(MediaNavigationStripComponent(
index: max(0, min(index, currentSlice.totalCount - 1)),
count: currentSlice.totalCount
index: max(0, min(index, component.slice.totalCount - 1)),
count: component.slice.totalCount
)),
environment: {
MediaNavigationStripComponent.EnvironmentType(
@ -1423,53 +1321,45 @@ public final class StoryItemSetContainerComponent: Component {
transition.setAlpha(view: navigationStripView, alpha: component.hideUI ? 0.0 : 1.0)
}
if let focusedItemId = self.focusedItemId, let focusedItem = self.currentSlice?.items.first(where: { $0.id == focusedItemId }) {
var items: [StoryActionsComponent.Item] = []
let _ = focusedItem
/*if !focusedItem.isMy {
items.append(StoryActionsComponent.Item(
kind: .like,
isActivated: focusedItem.hasLike
))
}*/
items.append(StoryActionsComponent.Item(
kind: .share,
isActivated: false
))
let inlineActionsSize = self.inlineActions.update(
transition: transition,
component: AnyComponent(StoryActionsComponent(
items: items,
action: { [weak self] item in
guard let self else {
return
}
self.sendMessageContext.performInlineAction(view: self, item: item)
var items: [StoryActionsComponent.Item] = []
let _ = focusedItem
items.append(StoryActionsComponent.Item(
kind: .share,
isActivated: false
))
let inlineActionsSize = self.inlineActions.update(
transition: transition,
component: AnyComponent(StoryActionsComponent(
items: items,
action: { [weak self] item in
guard let self else {
return
}
)),
environment: {},
containerSize: contentFrame.size
)
if let inlineActionsView = self.inlineActions.view {
if inlineActionsView.superview == nil {
self.contentContainerView.addSubview(inlineActionsView)
self.sendMessageContext.performInlineAction(view: self, item: item)
}
transition.setFrame(view: inlineActionsView, frame: CGRect(origin: CGPoint(x: contentFrame.width - 10.0 - inlineActionsSize.width, y: contentFrame.height - 20.0 - inlineActionsSize.height), size: inlineActionsSize))
var inlineActionsAlpha: CGFloat = inputPanelIsOverlay ? 0.0 : 1.0
if self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil {
inlineActionsAlpha = 0.0
}
if self.displayReactions {
inlineActionsAlpha = 0.0
}
if component.hideUI {
inlineActionsAlpha = 0.0
}
transition.setAlpha(view: inlineActionsView, alpha: inlineActionsAlpha)
)),
environment: {},
containerSize: contentFrame.size
)
if let inlineActionsView = self.inlineActions.view {
if inlineActionsView.superview == nil {
self.contentContainerView.addSubview(inlineActionsView)
}
transition.setFrame(view: inlineActionsView, frame: CGRect(origin: CGPoint(x: contentFrame.width - 10.0 - inlineActionsSize.width, y: contentFrame.height - 20.0 - inlineActionsSize.height), size: inlineActionsSize))
var inlineActionsAlpha: CGFloat = inputPanelIsOverlay ? 0.0 : 1.0
if self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil {
inlineActionsAlpha = 0.0
}
if self.displayReactions {
inlineActionsAlpha = 0.0
}
if component.hideUI {
inlineActionsAlpha = 0.0
}
transition.setAlpha(view: inlineActionsView, alpha: inlineActionsAlpha)
}
}

View File

@ -59,9 +59,7 @@ final class StoryItemSetContainerSendMessage {
guard let component = view.component else {
return
}
guard let focusedItemId = view.focusedItemId, let focusedItem = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else {
return
}
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return
}
@ -117,9 +115,7 @@ final class StoryItemSetContainerSendMessage {
guard let component = view.component else {
return
}
guard let focusedItemId = view.focusedItemId, let focusedItem = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else {
return
}
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return
}
@ -335,9 +331,7 @@ final class StoryItemSetContainerSendMessage {
guard let component = view.component else {
return
}
guard let focusedItemId = view.focusedItemId, let focusedItem = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else {
return
}
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return
}
@ -1582,10 +1576,6 @@ final class StoryItemSetContainerSendMessage {
}
private func transformEnqueueMessages(view: StoryItemSetContainerComponent.View, messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] {
guard let focusedItemId = view.focusedItemId, let _ = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else {
return []
}
let defaultReplyMessageId: EngineMessage.Id? = nil
return messages.map { message in

View File

@ -8,61 +8,6 @@ import TelegramCore
import Postbox
import StoryContainerScreen
public final class StoryContentContextState {
public final class FocusedSlice {
public let peer: EnginePeer
public let item: StoryContentItem
public let totalCount: Int
public let previousItemId: Int32?
public let nextItemId: Int32?
public init(
peer: EnginePeer,
item: StoryContentItem,
totalCount: Int,
previousItemId: Int32?,
nextItemId: Int32?
) {
self.peer = peer
self.item = item
self.totalCount = totalCount
self.previousItemId = previousItemId
self.nextItemId = nextItemId
}
}
public let slice: FocusedSlice?
public let previousSlice: FocusedSlice?
public let nextSlice: FocusedSlice?
public init(
slice: FocusedSlice?,
previousSlice: FocusedSlice?,
nextSlice: FocusedSlice?
) {
self.slice = slice
self.previousSlice = previousSlice
self.nextSlice = nextSlice
}
}
public enum StoryContentContextNavigation {
public enum Direction {
case previous
case next
}
case item(Direction)
case peer(Direction)
}
public protocol StoryContentContext {
var stateValue: StoryContentContextState? { get }
var state: Signal<StoryContentContextState, NoError> { get }
func navigate(navigation: StoryContentContextNavigation)
}
public final class StoryContentContextImpl: StoryContentContext {
private struct StoryKey: Hashable {
var peerId: EnginePeer.Id
@ -77,25 +22,39 @@ public final class StoryContentContextImpl: StoryContentContext {
let updated = Promise<Void>()
var isReady: Bool {
return false
}
private(set) var isReady: Bool = false
private var disposable: Disposable?
private var loadDisposable: Disposable?
init(context: AccountContext, peerId: EnginePeer.Id, focusedId: Int32?, loadIds: @escaping ([StoryKey]) -> Void) {
private let currentFocusedIdPromise = Promise<Int32?>()
private var storedFocusedId: Int32?
var currentFocusedId: Int32? {
didSet {
if self.currentFocusedId != self.storedFocusedId {
self.storedFocusedId = self.currentFocusedId
self.currentFocusedIdPromise.set(.single(self.currentFocusedId))
}
}
}
init(context: AccountContext, peerId: EnginePeer.Id, focusedId initialFocusedId: Int32?, loadIds: @escaping ([StoryKey]) -> Void) {
self.context = context
self.peerId = peerId
self.disposable = (context.account.postbox.combinedView(
keys: [
PostboxViewKey.basicPeer(peerId),
PostboxViewKey.storiesState(key: .peer(peerId)),
PostboxViewKey.storyItems(peerId: peerId)
]
self.currentFocusedIdPromise.set(.single(initialFocusedId))
self.disposable = (combineLatest(queue: .mainQueue(),
self.currentFocusedIdPromise.get(),
context.account.postbox.combinedView(
keys: [
PostboxViewKey.basicPeer(peerId),
PostboxViewKey.storiesState(key: .peer(peerId)),
PostboxViewKey.storyItems(peerId: peerId)
]
)
)
|> deliverOnMainQueue).start(next: { [weak self] views in
|> deliverOnMainQueue).start(next: { [weak self] currentFocusedId, views in
guard let self else {
return
}
@ -114,11 +73,15 @@ public final class StoryContentContextImpl: StoryContentContext {
let state = stateView.value?.get(Stories.PeerState.self)
var focusedIndex: Int?
if let focusedId {
focusedIndex = itemsView.items.firstIndex(where: { $0.id == focusedId })
if let currentFocusedId {
focusedIndex = itemsView.items.firstIndex(where: { $0.id == currentFocusedId })
}
if focusedIndex == nil, let state {
focusedIndex = itemsView.items.firstIndex(where: { $0.id >= state.maxReadId })
if let storedFocusedId = self.storedFocusedId {
focusedIndex = itemsView.items.firstIndex(where: { $0.id >= storedFocusedId })
} else {
focusedIndex = itemsView.items.firstIndex(where: { $0.id > state.maxReadId })
}
}
if focusedIndex == nil {
if !itemsView.items.isEmpty {
@ -127,6 +90,8 @@ public final class StoryContentContextImpl: StoryContentContext {
}
if let focusedIndex {
self.storedFocusedId = itemsView.items[focusedIndex].id
var previousItemId: Int32?
var nextItemId: Int32?
@ -198,8 +163,12 @@ public final class StoryContentContextImpl: StoryContentContext {
previousItemId: previousItemId,
nextItemId: nextItemId
)
self.isReady = true
self.updated.set(.single(Void()))
}
} else {
self.isReady = true
self.updated.set(.single(Void()))
}
})
}
@ -221,12 +190,6 @@ public final class StoryContentContextImpl: StoryContentContext {
if !self.centralPeerContext.isReady {
return false
}
if let previousPeerContext = self.previousPeerContext, !previousPeerContext.isReady {
return false
}
if let nextPeerContext = self.nextPeerContext, !nextPeerContext.isReady {
return false
}
return true
}
@ -277,6 +240,19 @@ public final class StoryContentContextImpl: StoryContentContext {
self.previousDisposable?.dispose()
self.nextDisposable?.dispose()
}
func findPeerContext(id: EnginePeer.Id) -> PeerContext? {
if self.centralPeerContext.sliceValue?.peer.id == id {
return self.centralPeerContext
}
if let previousPeerContext = self.previousPeerContext, previousPeerContext.sliceValue?.peer.id == id {
return previousPeerContext
}
if let nextPeerContext = self.nextPeerContext, nextPeerContext.sliceValue?.peer.id == id {
return nextPeerContext
}
return nil
}
}
private let context: AccountContext
@ -287,6 +263,11 @@ public final class StoryContentContextImpl: StoryContentContext {
}
private let statePromise = Promise<StoryContentContextState>()
private let updatedPromise = Promise<Void>()
public var updated: Signal<Void, NoError> {
return self.updatedPromise.get()
}
private var focusedItem: (peerId: EnginePeer.Id, storyId: Int32?)?
private var currentState: StateContext?
@ -336,42 +317,33 @@ public final class StoryContentContextImpl: StoryContentContext {
private func switchToFocusedPeerId() {
if let storySubscriptions = self.storySubscriptions {
if self.pendingState == nil {
var centralIndex: Int?
if let (focusedPeerId, _) = self.focusedItem {
if let index = storySubscriptions.items.firstIndex(where: { $0.peer.id == focusedPeerId }) {
centralIndex = index
let loadIds: ([StoryKey]) -> Void = { [weak self] keys in
guard let self else {
return
}
}
if centralIndex == nil {
if !storySubscriptions.items.isEmpty {
centralIndex = 0
let missingKeys = Set(keys).subtracting(self.requestedStoryKeys)
if !missingKeys.isEmpty {
var idsByPeerId: [EnginePeer.Id: [Int32]] = [:]
for key in missingKeys {
if idsByPeerId[key.peerId] == nil {
idsByPeerId[key.peerId] = [key.id]
} else {
idsByPeerId[key.peerId]?.append(key.id)
}
}
for (peerId, ids) in idsByPeerId {
self.requestStoryDisposables.add(self.context.engine.messages.refreshStories(peerId: peerId, ids: ids).start())
}
}
}
if let centralIndex {
let loadIds: ([StoryKey]) -> Void = { [weak self] keys in
guard let self else {
return
}
let missingKeys = Set(keys).subtracting(self.requestedStoryKeys)
if !missingKeys.isEmpty {
var idsByPeerId: [EnginePeer.Id: [Int32]] = [:]
for key in missingKeys {
if idsByPeerId[key.peerId] == nil {
idsByPeerId[key.peerId] = [key.id]
} else {
idsByPeerId[key.peerId]?.append(key.id)
}
}
for (peerId, ids) in idsByPeerId {
self.requestStoryDisposables.add(self.context.engine.messages.refreshStories(peerId: peerId, ids: ids).start())
}
}
}
if let (focusedPeerId, _) = self.focusedItem, focusedPeerId == self.context.account.peerId {
let centralPeerContext = PeerContext(context: self.context, peerId: self.context.account.peerId, focusedId: nil, loadIds: loadIds)
let pendingState = StateContext(
centralPeerContext: PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex].peer.id, focusedId: nil, loadIds: loadIds),
previousPeerContext: centralIndex == 0 ? nil : PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex - 1].peer.id, focusedId: nil, loadIds: loadIds),
nextPeerContext: (centralIndex == storySubscriptions.items.count - 1) ? nil : PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex + 1].peer.id, focusedId: nil, loadIds: loadIds)
centralPeerContext: centralPeerContext,
previousPeerContext: nil,
nextPeerContext: nil
)
self.pendingState = pendingState
self.pendingStateReadyDisposable = (pendingState.updated.get()
@ -396,17 +368,143 @@ public final class StoryContentContextImpl: StoryContentContext {
self.updateState()
})
})
} else {
var centralIndex: Int?
if let (focusedPeerId, _) = self.focusedItem {
if let index = storySubscriptions.items.firstIndex(where: { $0.peer.id == focusedPeerId }) {
centralIndex = index
}
}
if centralIndex == nil {
if !storySubscriptions.items.isEmpty {
centralIndex = 0
}
}
if let centralIndex {
let centralPeerContext: PeerContext
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: storySubscriptions.items[centralIndex].peer.id) {
centralPeerContext = existingContext
} else {
centralPeerContext = PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex].peer.id, focusedId: nil, loadIds: loadIds)
}
var previousPeerContext: PeerContext?
if centralIndex != 0 {
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: storySubscriptions.items[centralIndex - 1].peer.id) {
previousPeerContext = existingContext
} else {
previousPeerContext = PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex - 1].peer.id, focusedId: nil, loadIds: loadIds)
}
}
var nextPeerContext: PeerContext?
if centralIndex != storySubscriptions.items.count - 1 {
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: storySubscriptions.items[centralIndex + 1].peer.id) {
nextPeerContext = existingContext
} else {
nextPeerContext = PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex + 1].peer.id, focusedId: nil, loadIds: loadIds)
}
}
let pendingState = StateContext(
centralPeerContext: centralPeerContext,
previousPeerContext: previousPeerContext,
nextPeerContext: nextPeerContext
)
self.pendingState = pendingState
self.pendingStateReadyDisposable = (pendingState.updated.get()
|> deliverOnMainQueue).start(next: { [weak self, weak pendingState] _ in
guard let self, let pendingState, self.pendingState === pendingState, pendingState.isReady else {
return
}
self.pendingState = nil
self.pendingStateReadyDisposable?.dispose()
self.pendingStateReadyDisposable = nil
self.currentState = pendingState
self.updateState()
self.currentStateUpdatedDisposable?.dispose()
self.currentStateUpdatedDisposable = (pendingState.updated.get()
|> deliverOnMainQueue).start(next: { [weak self, weak pendingState] _ in
guard let self, let pendingState, self.currentState === pendingState else {
return
}
self.updateState()
})
})
}
}
}
}
}
private func updateState() {
preconditionFailure()
guard let currentState = self.currentState else {
return
}
let stateValue = StoryContentContextState(
slice: currentState.centralPeerContext.sliceValue,
previousSlice: currentState.previousPeerContext?.sliceValue,
nextSlice: currentState.nextPeerContext?.sliceValue
)
self.stateValue = stateValue
self.statePromise.set(.single(stateValue))
self.updatedPromise.set(.single(Void()))
}
public func resetSideStates() {
guard let currentState = self.currentState else {
return
}
if let previousPeerContext = currentState.previousPeerContext {
previousPeerContext.currentFocusedId = nil
}
if let nextPeerContext = currentState.nextPeerContext {
nextPeerContext.currentFocusedId = nil
}
}
public func navigate(navigation: StoryContentContextNavigation) {
guard let currentState = self.currentState else {
return
}
switch navigation {
case let .peer(direction):
switch direction {
case .previous:
if let previousPeerContext = currentState.previousPeerContext, let previousSlice = previousPeerContext.sliceValue {
self.pendingStateReadyDisposable?.dispose()
self.pendingState = nil
self.focusedItem = (previousSlice.peer.id, nil)
self.switchToFocusedPeerId()
}
case .next:
if let nextPeerContext = currentState.nextPeerContext, let nextSlice = nextPeerContext.sliceValue {
self.pendingStateReadyDisposable?.dispose()
self.pendingState = nil
self.focusedItem = (nextSlice.peer.id, nil)
self.switchToFocusedPeerId()
}
}
case let .item(direction):
if let slice = currentState.centralPeerContext.sliceValue {
switch direction {
case .previous:
if let previousItemId = slice.previousItemId {
currentState.centralPeerContext.currentFocusedId = previousItemId
}
case .next:
if let nextItemId = slice.nextItemId {
currentState.centralPeerContext.currentFocusedId = nextItemId
}
}
}
}
}
}

View File

@ -156,6 +156,7 @@ final class StoryItemContentComponent: Component {
userLocation: .other,
fileReference: .story(peer: peerReference, id: component.item.id, media: file),
imageReference: nil,
streamVideo: .story,
loopVideo: true,
enableSound: true,
tempFilePath: nil,
@ -192,6 +193,15 @@ final class StoryItemContentComponent: Component {
}
}
override func rewind() {
self.currentProgressTimerValue = 0.0
if let videoNode = self.videoNode {
if self.contentLoaded {
videoNode.seek(0.0)
}
}
}
private func updateIsProgressPaused() {
if let videoNode = self.videoNode {
if !self.isProgressPaused && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy {
@ -229,7 +239,7 @@ final class StoryItemContentComponent: Component {
}
#if DEBUG// && false
let currentProgressTimerLimit: Double = 5 * 60.0
let currentProgressTimerLimit: Double = 1 * 60.0
#else
let currentProgressTimerLimit: Double = 5.0
#endif

View File

@ -189,14 +189,15 @@ public final class StoryPeerListComponent: Component {
}
var hasStories: Bool = false
var storyCount = 0
if let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty {
hasStories = true
storyCount = storySubscriptions.items.count
}
let titleSpacing: CGFloat = 8.0
let titleText: String
let storyCount = self.sortedItems.count
if storyCount <= 0 {
titleText = "No Stories"
} else {
@ -291,10 +292,15 @@ public final class StoryPeerListComponent: Component {
var hasUnseen = false
hasUnseen = itemSet.hasUnseen
let hasItems = true
var hasItems = true
var itemProgress: CGFloat?
if peer.id == component.context.account.peerId {
itemProgress = nil
if let storySubscriptions = component.storySubscriptions, let accountItem = storySubscriptions.accountItem {
hasItems = accountItem.storyCount != 0
} else {
hasItems = false
}
//itemProgress = component.state?.uploadProgress
//itemProgress = 0.0
}
@ -421,9 +427,10 @@ public final class StoryPeerListComponent: Component {
self.sortedItems.removeAll(keepingCapacity: true)
if let storySubscriptions = component.storySubscriptions {
if let myIndex = storySubscriptions.items.firstIndex(where: { $0.peer.id == component.context.account.peerId }) {
self.sortedItems.append(storySubscriptions.items[myIndex])
if let accountItem = storySubscriptions.accountItem {
self.sortedItems.append(accountItem)
}
for itemSet in storySubscriptions.items {
if itemSet.peer.id == component.context.account.peerId {
continue