mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
[WIP] Stories
This commit is contained in:
parent
a5e8db4f2b
commit
39963f3a6c
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user