From 04b05b510a1fdbaad607eccc5579807dcdd9597b Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 26 Jun 2023 01:58:08 +0300 Subject: [PATCH] Stories --- submodules/ChatListUI/BUILD | 1 - .../Sources/ChatListController.swift | 1 - submodules/ContactListUI/BUILD | 1 - .../Sources/ContactsController.swift | 1 - submodules/GalleryData/BUILD | 1 - .../GalleryData/Sources/GalleryData.swift | 1 - .../NotificationsPeerCategoryController.swift | 118 +++- .../TelegramEngine/Messages/Stories.swift | 2 +- .../Peers/TelegramEnginePeers.swift | 9 + submodules/TelegramUI/BUILD | 1 - .../NotificationPeerExceptionController.swift | 7 +- .../PeerInfoVisualMediaPaneNode/BUILD | 1 - .../Sources/PeerInfoStoryPaneNode.swift | 1 - .../Sources/StoryAuthorInfoComponent.swift | 0 .../Sources/StoryAvatarInfoComponent.swift | 0 .../Sources/StoryChatContent.swift | 101 ++- .../Sources/StoryContainerScreen.swift | 27 +- .../Sources/StoryContent.swift | 80 +-- .../Sources/StoryItemContentComponent.swift | 68 +- .../StoryItemSetContainerComponent.swift | 626 ++++++++++++++---- .../Sources/StoryPositionInfoComponent.swift | 0 .../Sources/StoryVideoDecoration.swift | 0 .../Stories/StoryContentComponent/BUILD | 33 - .../TelegramUI/Sources/ChatController.swift | 1 - .../TelegramUI/Sources/OpenChatMessage.swift | 1 - .../TelegramUI/Sources/OpenResolvedUrl.swift | 1 - .../Sources/PeerInfo/PeerInfoScreen.swift | 1 - 27 files changed, 715 insertions(+), 369 deletions(-) rename submodules/TelegramUI/Components/Stories/{StoryContentComponent => StoryContainerScreen}/Sources/StoryAuthorInfoComponent.swift (100%) rename submodules/TelegramUI/Components/Stories/{StoryContentComponent => StoryContainerScreen}/Sources/StoryAvatarInfoComponent.swift (100%) rename submodules/TelegramUI/Components/Stories/{StoryContentComponent => StoryContainerScreen}/Sources/StoryChatContent.swift (94%) rename submodules/TelegramUI/Components/Stories/{StoryContentComponent => StoryContainerScreen}/Sources/StoryItemContentComponent.swift (95%) rename submodules/TelegramUI/Components/Stories/{StoryContentComponent => StoryContainerScreen}/Sources/StoryPositionInfoComponent.swift (100%) rename submodules/TelegramUI/Components/Stories/{StoryContentComponent => StoryContainerScreen}/Sources/StoryVideoDecoration.swift (100%) delete mode 100644 submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index a015d5cd04..490ff5d855 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -94,7 +94,6 @@ swift_library( "//submodules/QrCodeUI", "//submodules/TelegramUI/Components/ActionPanelComponent", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", - "//submodules/TelegramUI/Components/Stories/StoryContentComponent", "//submodules/TelegramUI/Components/Stories/StoryPeerListComponent", "//submodules/TelegramUI/Components/FullScreenEffectView", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 2dbf113703..12c1d3ccbb 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -46,7 +46,6 @@ import ChatListTitleView import InviteLinksUI import ChatFolderLinkPreviewScreen import StoryContainerScreen -import StoryContentComponent import FullScreenEffectView private final class ContextControllerContentSourceImpl: ContextControllerContentSource { diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD index fb00b0da66..ff7d9bb750 100644 --- a/submodules/ContactListUI/BUILD +++ b/submodules/ContactListUI/BUILD @@ -38,7 +38,6 @@ swift_library( "//submodules/QrCodeUI:QrCodeUI", "//submodules/LocalizedPeerData:LocalizedPeerData", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", - "//submodules/TelegramUI/Components/Stories/StoryContentComponent", "//submodules/TelegramUI/Components/Stories/StoryPeerListComponent", "//submodules/TelegramUI/Components/ChatListTitleView", "//submodules/TelegramUI/Components/ChatListHeaderComponent", diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index ea7bf6078c..47d3505934 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -20,7 +20,6 @@ import StickerResources import ContextUI import QrCodeUI import StoryContainerScreen -import StoryContentComponent import ChatListHeaderComponent private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { diff --git a/submodules/GalleryData/BUILD b/submodules/GalleryData/BUILD index 6c8d7399cc..ec0ae7d4d8 100644 --- a/submodules/GalleryData/BUILD +++ b/submodules/GalleryData/BUILD @@ -25,7 +25,6 @@ swift_library( "//submodules/MediaResources:MediaResources", "//submodules/WebsiteType:WebsiteType", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", - "//submodules/TelegramUI/Components/Stories/StoryContentComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 3d45808e8f..6dec02a52b 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -15,7 +15,6 @@ import GalleryUI import MediaResources import WebsiteType import StoryContainerScreen -import StoryContentComponent public enum ChatMessageGalleryControllerData { case url(String) diff --git a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift index fc22468a53..740b463a24 100644 --- a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift +++ b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift @@ -598,19 +598,35 @@ public func notificationsPeerCategoryController(context: AccountContext, categor updateNotificationsView({}) }) }, removePeerFromExceptions: { - let _ = ( - context.engine.peers.removeCustomNotificationSettings(peerIds: [peerId]) - |> map { _ -> EnginePeer? in } - |> then(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))) - ).start(next: { peer in - guard let peer = peer else { - return - } - updateState { value in - return value.withUpdatedPeerDisplayPreviews(peer, .default).withUpdatedPeerSound(peer, .default).withUpdatedPeerMuteInterval(peer, nil) - } - updateNotificationsView({}) - }) + if case .stories = mode.mode { + let _ = ( + context.engine.peers.removeCustomStoryNotificationSettings(peerIds: [peerId]) + |> map { _ -> EnginePeer? in } + |> then(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))) + ).start(next: { peer in + guard let peer = peer else { + return + } + updateState { value in + return value.withUpdatedPeerStoryNotifications(peer, .default) + } + updateNotificationsView({}) + }) + } else { + let _ = ( + context.engine.peers.removeCustomNotificationSettings(peerIds: [peerId]) + |> map { _ -> EnginePeer? in } + |> then(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))) + ).start(next: { peer in + guard let peer = peer else { + return + } + updateState { value in + return value.withUpdatedPeerDisplayPreviews(peer, .default).withUpdatedPeerSound(peer, .default).withUpdatedPeerMuteInterval(peer, nil) + } + updateNotificationsView({}) + }) + } }, modifiedPeer: { })) @@ -699,21 +715,39 @@ public func notificationsPeerCategoryController(context: AccountContext, categor let values = stateValue.with { $0.mode.settings.values } - let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { $0.peer }) - |> deliverOnMainQueue).start(completed: { - updateNotificationsDisposable.set(nil) - updateState { state in - var state = state - for value in values { - state = state.withUpdatedPeerMuteInterval(value.peer, nil).withUpdatedPeerSound(value.peer, .default).withUpdatedPeerDisplayPreviews(value.peer, .default) - } - return state - } - let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: values.map(\.peer.id)) + if case .stories = mode.mode { + let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { $0.peer }) |> deliverOnMainQueue).start(completed: { - updateNotificationsView({}) + updateNotificationsDisposable.set(nil) + updateState { state in + var state = state + for value in values { + state = state.withUpdatedPeerStoryNotifications(value.peer, .default) + } + return state + } + let _ = (context.engine.peers.removeCustomStoryNotificationSettings(peerIds: values.map(\.peer.id)) + |> deliverOnMainQueue).start(completed: { + updateNotificationsView({}) + }) }) - }) + } else { + let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { $0.peer }) + |> deliverOnMainQueue).start(completed: { + updateNotificationsDisposable.set(nil) + updateState { state in + var state = state + for value in values { + state = state.withUpdatedPeerMuteInterval(value.peer, nil).withUpdatedPeerSound(value.peer, .default).withUpdatedPeerDisplayPreviews(value.peer, .default) + } + return state + } + let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: values.map(\.peer.id)) + |> deliverOnMainQueue).start(completed: { + updateNotificationsView({}) + }) + }) + } }) ]), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in @@ -726,17 +760,31 @@ public func notificationsPeerCategoryController(context: AccountContext, categor return current.withUpdatedRevealedPeerId(peerId) } }, removePeer: { peer in - let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [peer]) - |> deliverOnMainQueue).start(completed: { - updateNotificationsDisposable.set(nil) - updateState { value in - return value.withUpdatedPeerMuteInterval(peer, nil).withUpdatedPeerSound(peer, .default).withUpdatedPeerDisplayPreviews(peer, .default) - } - let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: [peer.id]) + if case .stories = mode.mode { + let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [peer]) |> deliverOnMainQueue).start(completed: { - updateNotificationsView({}) + updateNotificationsDisposable.set(nil) + updateState { value in + return value.withUpdatedPeerStoryNotifications(peer, .default) + } + let _ = (context.engine.peers.removeCustomStoryNotificationSettings(peerIds: [peer.id]) + |> deliverOnMainQueue).start(completed: { + updateNotificationsView({}) + }) }) - }) + } else { + let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [peer]) + |> deliverOnMainQueue).start(completed: { + updateNotificationsDisposable.set(nil) + updateState { value in + return value.withUpdatedPeerMuteInterval(peer, nil).withUpdatedPeerSound(peer, .default).withUpdatedPeerDisplayPreviews(peer, .default) + } + let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: [peer.id]) + |> deliverOnMainQueue).start(completed: { + updateNotificationsView({}) + }) + }) + } }, updatedExceptionMode: { mode in _ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels, stories) in switch mode { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 62a2baf099..ae962bed6a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1103,7 +1103,7 @@ func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32, asPi account.stateManager.injectStoryUpdates(updates: [.read(peerId: peerId, maxId: id)]) - #if DEBUG && true + #if DEBUG && false if "".isEmpty { return .complete() } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 109c5caba8..d6ffec8417 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -302,6 +302,15 @@ public extension TelegramEngine { } |> ignoreValues } + + public func removeCustomStoryNotificationSettings(peerIds: [PeerId]) -> Signal { + return self.account.postbox.transaction { transaction -> Void in + for peerId in peerIds { + _internal_updatePeerStoriesMutedSetting(account: self.account, transaction: transaction, peerId: peerId, isMuted: nil) + } + } + |> ignoreValues + } public func channelAdminEventLog(peerId: PeerId) -> ChannelAdminEventLogContext { return ChannelAdminEventLogContext(postbox: self.account.postbox, network: self.account.network, peerId: peerId) diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index a2e02b02c6..794b2bfcdf 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -359,7 +359,6 @@ swift_library( "//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen", "//submodules/TelegramUI/Components/SliderContextItem:SliderContextItem", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", - "//submodules/TelegramUI/Components/Stories/StoryContentComponent", "//submodules/TelegramUI/Components/CameraScreen", "//submodules/TelegramUI/Components/MediaEditorScreen", "//submodules/TelegramUI/Components/ChatScheduleTimeController", diff --git a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift index 9e387d7854..b7dd896922 100644 --- a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift +++ b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift @@ -249,12 +249,7 @@ public enum NotificationExceptionMode : Equatable { if let value = values[peerId] { switch storyNotifications { case .default: - switch value.settings.storiesMuted { - case .none: - values.removeValue(forKey: peerId) - default: - values[peerId] = value.updateSettings({$0.withUpdatedStoriesMuted(storiesMuted)}).withUpdatedDate(Date().timeIntervalSince1970) - } + values.removeValue(forKey: peerId) default: values[peerId] = value.updateSettings({$0.withUpdatedStoriesMuted(storiesMuted)}).withUpdatedDate(Date().timeIntervalSince1970) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD index 35136edf64..712da58eaa 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD @@ -36,7 +36,6 @@ swift_library( "//submodules/InvisibleInkDustNode", "//submodules/MediaPickerUI", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", - "//submodules/TelegramUI/Components/Stories/StoryContentComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index d341d3ecbb..77f5e9b15a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -26,7 +26,6 @@ import AppBundle import InvisibleInkDustNode import MediaPickerUI import StoryContainerScreen -import StoryContentComponent private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeTextColor = UIColor.white diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAuthorInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift similarity index 100% rename from submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAuthorInfoComponent.swift rename to submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAvatarInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAvatarInfoComponent.swift similarity index 100% rename from submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAvatarInfoComponent.swift rename to submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAvatarInfoComponent.swift diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift similarity index 94% rename from submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift rename to submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index ef728d81df..f465ab18bc 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -6,7 +6,6 @@ import SwiftSignalKit import AccountContext import TelegramCore import Postbox -import StoryContainerScreen private struct StoryKey: Hashable { var peerId: EnginePeer.Id @@ -245,34 +244,27 @@ public final class StoryContentContextImpl: StoryContentContext { } } + let allItems = mappedItems.map { item in + return StoryContentItem( + position: nil, + peerId: peer.id, + storyItem: item + ) + } + self.nextItems = nextItems self.sliceValue = StoryContentContextState.FocusedSlice( peer: peer, additionalPeerData: additionalPeerData, item: StoryContentItem( - id: AnyHashable(mappedItem.id), position: focusedIndex, - component: AnyComponent(StoryItemContentComponent( - context: context, - peer: peer, - item: mappedItem - )), - centerInfoComponent: AnyComponent(StoryAuthorInfoComponent( - context: context, - peer: peer, - timestamp: mappedItem.timestamp - )), - rightInfoComponent: AnyComponent(StoryAvatarInfoComponent( - context: context, - peer: peer - )), peerId: peer.id, - storyItem: mappedItem, - isMy: peerId == context.account.peerId + storyItem: mappedItem ), totalCount: mappedItems.count, previousItemId: previousItemId, - nextItemId: nextItemId + nextItemId: nextItemId, + allItems: allItems ) self.isReady = true self.updated.set(.single(Void())) @@ -849,6 +841,10 @@ public final class StoryContentContextImpl: StoryContentContext { if let nextItemId = slice.nextItemId { currentState.centralPeerContext.currentFocusedId = nextItemId } + case let .id(id): + if slice.allItems.contains(where: { $0.storyItem.id == id }) { + currentState.centralPeerContext.currentFocusedId = id + } } } } @@ -961,34 +957,20 @@ public final class SingleStoryContentContextImpl: StoryContentContext { isCloseFriends: itemValue.isCloseFriends ) + let mainItem = StoryContentItem( + position: 0, + peerId: peer.id, + storyItem: mappedItem + ) let stateValue = StoryContentContextState( slice: StoryContentContextState.FocusedSlice( peer: peer, additionalPeerData: additionalPeerData, - item: StoryContentItem( - id: AnyHashable(item.id), - position: 0, - component: AnyComponent(StoryItemContentComponent( - context: context, - peer: peer, - item: mappedItem - )), - centerInfoComponent: AnyComponent(StoryAuthorInfoComponent( - context: context, - peer: peer, - timestamp: item.timestamp - )), - rightInfoComponent: AnyComponent(StoryAvatarInfoComponent( - context: context, - peer: peer - )), - peerId: peer.id, - storyItem: mappedItem, - isMy: peer.id == context.account.peerId - ), + item: mainItem, totalCount: 1, previousItemId: nil, - nextItemId: nil + nextItemId: nil, + allItems: [mainItem] ), previousSlice: nil, nextSlice: nil @@ -1124,34 +1106,27 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { let item = state.items[focusedIndex] self.focusedId = item.id + let allItems = state.items.map { stateItem -> StoryContentItem in + return StoryContentItem( + position: nil, + peerId: peer.id, + storyItem: stateItem + ) + } + stateValue = StoryContentContextState( slice: StoryContentContextState.FocusedSlice( peer: peer, additionalPeerData: additionalPeerData, item: StoryContentItem( - id: AnyHashable(item.id), position: nil, - component: AnyComponent(StoryItemContentComponent( - context: context, - peer: peer, - item: item - )), - centerInfoComponent: AnyComponent(StoryPositionInfoComponent( - context: context, - position: focusedIndex, - totalCount: state.totalCount - )), - rightInfoComponent: AnyComponent(StoryAvatarInfoComponent( - context: context, - peer: peer - )), peerId: peer.id, - storyItem: item, - isMy: peerId == self.context.account.peerId + storyItem: item ), totalCount: state.totalCount, previousItemId: focusedIndex == 0 ? nil : state.items[focusedIndex - 1].id, - nextItemId: (focusedIndex == state.items.count - 1) ? nil : state.items[focusedIndex + 1].id + nextItemId: (focusedIndex == state.items.count - 1) ? nil : state.items[focusedIndex + 1].id, + allItems: allItems ), previousSlice: nil, nextSlice: nil @@ -1283,15 +1258,19 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { case .peer: break case let .item(direction): - let indexDifference: Int + var indexDifference: Int? switch direction { case .next: indexDifference = 1 case .previous: indexDifference = -1 + case let .id(id): + if let listState = self.listState, let focusedId = self.focusedId, let index = listState.items.firstIndex(where: { $0.id == focusedId }), let nextIndex = listState.items.firstIndex(where: { $0.id == id }) { + indexDifference = nextIndex - index + } } - if let listState = self.listState, let focusedId = self.focusedId { + if let indexDifference, let listState = self.listState, let focusedId = self.focusedId { if let index = listState.items.firstIndex(where: { $0.id == focusedId }) { var nextIndex = index + indexDifference if nextIndex < 0 { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index b3a062a465..499fa9d476 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -75,14 +75,14 @@ private final class StoryContainerScreenComponent: Component { let content: StoryContentContext let focusedItemPromise: Promise let transitionIn: StoryContainerScreen.TransitionIn? - let transitionOut: (EnginePeer.Id, AnyHashable) -> StoryContainerScreen.TransitionOut? + let transitionOut: (EnginePeer.Id, Int32) -> StoryContainerScreen.TransitionOut? init( context: AccountContext, content: StoryContentContext, focusedItemPromise: Promise, transitionIn: StoryContainerScreen.TransitionIn?, - transitionOut: @escaping (EnginePeer.Id, AnyHashable) -> StoryContainerScreen.TransitionOut? + transitionOut: @escaping (EnginePeer.Id, Int32) -> StoryContainerScreen.TransitionOut? ) { self.context = context self.content = content @@ -322,7 +322,7 @@ private final class StoryContainerScreenComponent: Component { 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? + var direction: StoryContentContextNavigation.PeerDirection? if abs(velocity.x) > 10.0 { if velocity.x < 0.0 { if stateValue.nextSlice != nil { @@ -397,16 +397,25 @@ private final class StoryContainerScreenComponent: Component { let velocity = recognizer.velocity(in: self) self.verticalPanState = nil - self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) + var updateState = true if translation.y > 100.0 || velocity.y > 10.0 { + self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) self.environment?.controller()?.dismiss() } else if translation.y < -100.0 || velocity.y < -40.0 { if 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.activateInput() + if itemSetComponentView.activateInput() { + updateState = false + } } } + + if updateState || "".isEmpty { + self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) + } + } else { + self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) } default: break @@ -532,7 +541,7 @@ private final class StoryContainerScreenComponent: Component { func animateOut(completion: @escaping () -> Void) { self.isAnimatingOut = true - if !self.dismissWithoutTransitionOut, 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) { + if !self.dismissWithoutTransitionOut, 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.storyItem.id) { self.state?.updated(transition: .immediate) let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut)) @@ -552,7 +561,7 @@ private final class StoryContainerScreenComponent: Component { focusedItemPromise.set(.single(nil)) }) } else { - if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let transitionOut = component.transitionOut(slice.peer.id, slice.item.id) { + if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let transitionOut = component.transitionOut(slice.peer.id, slice.item.storyItem.id) { transitionOut.completed() } @@ -828,12 +837,14 @@ private final class StoryContainerScreenComponent: Component { self.commitHorizontalPan(velocity: CGPoint(x: 100.0, y: 0.0)) } } else { - let mappedDirection: StoryContentContextNavigation.Direction + let mappedDirection: StoryContentContextNavigation.ItemDirection switch direction { case .previous: mappedDirection = .previous case .next: mappedDirection = .next + case let .id(id): + mappedDirection = .id(id) } component.content.navigate(navigation: .item(mappedDirection)) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index f51a16f98f..124c748f53 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -7,7 +7,7 @@ import TelegramCore import Postbox import TelegramPresentationData -public final class StoryContentItem { +public final class StoryContentItem: Equatable { public final class ExternalState { public init() { } @@ -66,61 +66,31 @@ public final class StoryContentItem { } } - public let id: AnyHashable public let position: Int? - public let component: AnyComponent - public let centerInfoComponent: AnyComponent? - public let rightInfoComponent: AnyComponent? public let peerId: EnginePeer.Id? public let storyItem: EngineStoryItem - public let isMy: Bool public init( - id: AnyHashable, position: Int?, - component: AnyComponent, - centerInfoComponent: AnyComponent?, - rightInfoComponent: AnyComponent?, peerId: EnginePeer.Id?, - storyItem: EngineStoryItem, - isMy: Bool + storyItem: EngineStoryItem ) { - self.id = id self.position = position - self.component = component - self.centerInfoComponent = centerInfoComponent - self.rightInfoComponent = rightInfoComponent self.peerId = peerId self.storyItem = storyItem - self.isMy = isMy } -} - -public final class StoryContentItemSlice { - public let id: AnyHashable - public let focusedItemId: AnyHashable? - public let items: [StoryContentItem] - public let totalCount: Int - public let previousItemId: AnyHashable? - public let nextItemId: AnyHashable? - public let update: (StoryContentItemSlice, AnyHashable) -> Signal - - public init( - id: AnyHashable, - focusedItemId: AnyHashable?, - items: [StoryContentItem], - totalCount: Int, - previousItemId: AnyHashable?, - nextItemId: AnyHashable?, - update: @escaping (StoryContentItemSlice, AnyHashable) -> Signal - ) { - self.id = id - self.focusedItemId = focusedItemId - self.items = items - self.totalCount = totalCount - self.previousItemId = previousItemId - self.nextItemId = nextItemId - self.update = update + + public static func ==(lhs: StoryContentItem, rhs: StoryContentItem) -> Bool { + if lhs.position != rhs.position { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.storyItem != rhs.storyItem { + return false + } + return true } } @@ -149,6 +119,7 @@ public final class StoryContentContextState { public let totalCount: Int public let previousItemId: Int32? public let nextItemId: Int32? + public let allItems: [StoryContentItem] public init( peer: EnginePeer, @@ -156,7 +127,8 @@ public final class StoryContentContextState { item: StoryContentItem, totalCount: Int, previousItemId: Int32?, - nextItemId: Int32? + nextItemId: Int32?, + allItems: [StoryContentItem] ) { self.peer = peer self.additionalPeerData = additionalPeerData @@ -164,6 +136,7 @@ public final class StoryContentContextState { self.totalCount = totalCount self.previousItemId = previousItemId self.nextItemId = nextItemId + self.allItems = allItems } public static func ==(lhs: FocusedSlice, rhs: FocusedSlice) -> Bool { @@ -173,7 +146,7 @@ public final class StoryContentContextState { if lhs.additionalPeerData != rhs.additionalPeerData { return false } - if lhs.item.storyItem != rhs.item.storyItem { + if lhs.item != rhs.item { return false } if lhs.totalCount != rhs.totalCount { @@ -185,6 +158,9 @@ public final class StoryContentContextState { if lhs.nextItemId != rhs.nextItemId { return false } + if lhs.allItems != rhs.allItems { + return false + } return true } } @@ -205,13 +181,19 @@ public final class StoryContentContextState { } public enum StoryContentContextNavigation { - public enum Direction { + public enum ItemDirection { + case previous + case next + case id(Int32) + } + + public enum PeerDirection { case previous case next } - case item(Direction) - case peer(Direction) + case item(ItemDirection) + case peer(PeerDirection) } public protocol StoryContentContext: AnyObject { diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift similarity index 95% rename from submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift rename to submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 5c42c04874..6913877b39 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -10,7 +10,6 @@ import PhotoResources import SwiftSignalKit import UniversalMediaPlayer import TelegramUniversalVideoContent -import StoryContainerScreen import HierarchyTrackingLayer import ButtonComponent import MultilineTextComponent @@ -19,6 +18,14 @@ import TelegramPresentationData final class StoryItemContentComponent: Component { typealias EnvironmentType = StoryContentItem.Environment + final class Hint { + let synchronousLoad: Bool + + init(synchronousLoad: Bool) { + self.synchronousLoad = synchronousLoad + } + } + let context: AccountContext let peer: EnginePeer let item: EngineStoryItem @@ -56,7 +63,7 @@ final class StoryItemContentComponent: Component { private var unsupportedText: ComponentView? private var unsupportedButton: ComponentView? - private var isProgressPaused: Bool = false + private var isProgressPaused: Bool = true private var currentProgressTimer: SwiftSignalKit.Timer? private var currentProgressTimerValue: Double = 0.0 private var videoProgressDisposable: Disposable? @@ -82,7 +89,7 @@ final class StoryItemContentComponent: Component { guard let self else { return } - self.updateIsProgressPaused() + self.updateIsProgressPaused(update: true) } } @@ -97,6 +104,17 @@ final class StoryItemContentComponent: Component { } private func performActionAfterImageContentLoaded(update: Bool) { + self.initializeVideoIfReady(update: update) + } + + private func initializeVideoIfReady(update: Bool) { + if self.videoNode != nil { + return + } + if self.isProgressPaused { + return + } + guard let component = self.component, let environment = self.environment, let currentMessageMedia = self.currentMessageMedia else { return } @@ -152,12 +170,26 @@ final class StoryItemContentComponent: Component { } } } + + if let videoNode = self.videoNode { + if self.videoProgressDisposable == nil { + self.videoProgressDisposable = (videoNode.status + |> deliverOnMainQueue).start(next: { [weak self] status in + guard let self, let status else { + return + } + + self.videoPlaybackStatus = status + self.updateVideoPlaybackProgress() + }) + } + } } override func setIsProgressPaused(_ isProgressPaused: Bool) { if self.isProgressPaused != isProgressPaused { self.isProgressPaused = isProgressPaused - self.updateIsProgressPaused() + self.updateIsProgressPaused(update: true) } } @@ -176,7 +208,7 @@ final class StoryItemContentComponent: Component { } } - private func updateIsProgressPaused() { + private func updateIsProgressPaused(update: Bool) { if let videoNode = self.videoNode { var canPlay = !self.isProgressPaused && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy if let component = self.component { @@ -192,6 +224,7 @@ final class StoryItemContentComponent: Component { } } + self.initializeVideoIfReady(update: update) self.updateVideoPlaybackProgress() self.updateProgressTimer() } @@ -324,6 +357,11 @@ final class StoryItemContentComponent: Component { let environment = environment[StoryContentItem.Environment.self].value self.environment = environment + var synchronousLoad = false + if let hint = transition.userData(Hint.self) { + synchronousLoad = hint.synchronousLoad + } + let peerReference = PeerReference(component.peer._asPeer()) var messageMedia: EngineMedia? @@ -353,7 +391,7 @@ final class StoryItemContentComponent: Component { postbox: component.context.account.postbox, userLocation: .other, photoReference: .story(peer: peerReference, id: component.item.id, media: image), - synchronousLoad: true, + synchronousLoad: synchronousLoad, highQuality: true ) if let representation = image.representations.last { @@ -377,7 +415,7 @@ final class StoryItemContentComponent: Component { videoReference: .story(peer: peerReference, id: component.item.id, media: file), onlyFullSize: false, useLargeThumbnail: true, - synchronousLoad: true, + synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true, overlayColor: nil, nilForEmptyResult: false, @@ -458,20 +496,6 @@ final class StoryItemContentComponent: Component { self.imageNode.frame = CGRect(origin: CGPoint(), size: availableSize) } - if let videoNode = self.videoNode { - if self.videoProgressDisposable == nil { - self.videoProgressDisposable = (videoNode.status - |> deliverOnMainQueue).start(next: { [weak self] status in - guard let self, let status else { - return - } - - self.videoPlaybackStatus = status - self.updateVideoPlaybackProgress() - }) - } - } - switch component.item.media { case .image, .file: if let unsupportedText = self.unsupportedText { @@ -565,7 +589,7 @@ final class StoryItemContentComponent: Component { self.backgroundColor = UIColor(rgb: 0x181818) } - self.updateIsProgressPaused() + self.updateIsProgressPaused(update: false) return availableSize } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 46ec27b2f1..21d8d7712b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -29,6 +29,7 @@ import TextFormat import LocalMediaResources import SaveToCameraRoll import BundleIconComponent +import PeerListItemComponent public final class StoryItemSetContainerComponent: Component { public final class ExternalState { @@ -42,6 +43,7 @@ public final class StoryItemSetContainerComponent: Component { public enum NavigationDirection { case previous case next + case id(Int32) } public struct PinchState: Equatable { @@ -181,19 +183,33 @@ public final class StoryItemSetContainerComponent: Component { struct ItemLayout { var size: CGSize + var contentFrame: CGRect + var contentVisualScale: CGFloat - init(size: CGSize) { + init( + size: CGSize, + contentFrame: CGRect, + contentVisualScale: CGFloat + ) { self.size = size + self.contentFrame = contentFrame + self.contentVisualScale = contentVisualScale } } final class VisibleItem { let externalState = StoryContentItem.ExternalState() + let contentContainerView: UIView let view = ComponentView() var currentProgress: Double = 0.0 var requestedNext: Bool = false init() { + self.contentContainerView = UIView() + self.contentContainerView.clipsToBounds = true + if #available(iOS 13.0, *) { + self.contentContainerView.layer.cornerCurve = .continuous + } } } @@ -224,10 +240,35 @@ public final class StoryItemSetContainerComponent: Component { } } - public final class View: UIView, UIGestureRecognizerDelegate { + private final class Scroller: UIScrollView { + override init(frame: CGRect) { + super.init(frame: frame) + + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.contentInsetAdjustmentBehavior = .never + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + + /*@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + }*/ + } + + public final class View: UIView, UIScrollViewDelegate, UIGestureRecognizerDelegate { let sendMessageContext: StoryItemSetContainerSendMessage - let contentContainerView: UIView + private let scroller: Scroller + + let itemsContainerView: UIView + let controlsContainerView: UIView let topContentGradientLayer: SimpleGradientLayer let bottomContentGradientLayer: SimpleGradientLayer let contentDimView: UIView @@ -249,6 +290,7 @@ public final class StoryItemSetContainerComponent: Component { let inputPanelExternalState = MessageInputPanelComponent.ExternalState() private let inputPanelBackground = ComponentView() + var preparingToDisplayViewList: Bool = false var displayViewList: Bool = false var viewList: ViewList? @@ -257,9 +299,10 @@ public final class StoryItemSetContainerComponent: Component { var itemLayout: ItemLayout? var ignoreScrolling: Bool = false - var visibleItems: [AnyHashable: VisibleItem] = [:] - - var preloadContexts: [AnyHashable: Disposable] = [:] + var visibleItems: [Int32: VisibleItem] = [:] + var trulyValidIds: [Int32] = [] + var scrollingOffsetX: CGFloat = 0.0 + var scrollingCenterX: CGFloat = 0.0 var reactionItems: [ReactionItem]? var reactionContextNode: ReactionContextNode? @@ -282,13 +325,25 @@ public final class StoryItemSetContainerComponent: Component { let transitionCloneContainerView: UIView + private var awaitingSwitchToId: (from: Int32, to: Int32)? + private var animateNextNavigationId: Int32? + private var initializedOffset: Bool = false + override init(frame: CGRect) { self.sendMessageContext = StoryItemSetContainerSendMessage() - self.contentContainerView = UIView() - self.contentContainerView.clipsToBounds = true + self.itemsContainerView = UIView() + + self.scroller = Scroller() + self.scroller.alwaysBounceHorizontal = true + self.scroller.showsVerticalScrollIndicator = false + self.scroller.showsHorizontalScrollIndicator = false + self.scroller.decelerationRate = .fast + + self.controlsContainerView = SparseContainerView() + self.controlsContainerView.clipsToBounds = true if #available(iOS 13.0, *) { - self.contentContainerView.layer.cornerCurve = .continuous + self.controlsContainerView.layer.cornerCurve = .continuous } self.topContentGradientLayer = SimpleGradientLayer() @@ -304,18 +359,26 @@ public final class StoryItemSetContainerComponent: Component { super.init(frame: frame) - self.addSubview(self.contentContainerView) - self.contentContainerView.addSubview(self.contentDimView) - self.contentContainerView.layer.addSublayer(self.topContentGradientLayer) + self.clipsToBounds = true + + self.itemsContainerView.addSubview(self.scroller) + self.scroller.delegate = self + self.itemsContainerView.addGestureRecognizer(self.scroller.panGestureRecognizer) + + self.addSubview(self.itemsContainerView) + self.addSubview(self.controlsContainerView) + + self.controlsContainerView.addSubview(self.contentDimView) + self.controlsContainerView.layer.addSublayer(self.topContentGradientLayer) self.layer.addSublayer(self.bottomContentGradientLayer) self.closeButton.addSubview(self.closeButtonIconView) - self.contentContainerView.addSubview(self.closeButton) + self.controlsContainerView.addSubview(self.closeButton) self.closeButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) tapRecognizer.delegate = self - self.contentContainerView.addGestureRecognizer(tapRecognizer) + self.itemsContainerView.addGestureRecognizer(tapRecognizer) self.audioRecorderDisposable = (self.sendMessageContext.audioRecorder.get() |> deliverOnMainQueue).start(next: { [weak self] audioRecorder in @@ -430,7 +493,7 @@ public final class StoryItemSetContainerComponent: Component { } } - if self.contentContainerView.frame.contains(point) { + if self.controlsContainerView.frame.contains(point) { return true } @@ -448,7 +511,7 @@ public final class StoryItemSetContainerComponent: Component { guard let component = self.component else { return } - guard let visibleItem = self.visibleItems[component.slice.item.id] else { + guard let visibleItem = self.visibleItems[component.slice.item.storyItem.id] else { return } if let itemView = visibleItem.view.view as? StoryContentItem.View { @@ -460,7 +523,7 @@ public final class StoryItemSetContainerComponent: Component { guard let component = self.component else { return } - guard let visibleItem = self.visibleItems[component.slice.item.id] else { + guard let visibleItem = self.visibleItems[component.slice.item.storyItem.id] else { return } if let itemView = visibleItem.view.view as? StoryContentItem.View { @@ -481,8 +544,21 @@ public final class StoryItemSetContainerComponent: Component { self.sendMessageContext.currentInputMode = .text self.endEditing(true) } else if self.displayViewList { - self.displayViewList = false - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + let point = recognizer.location(in: self) + + for (id, visibleItem) in self.visibleItems { + if visibleItem.contentContainerView.convert(visibleItem.contentContainerView.bounds, to: self).contains(point) { + if id == component.slice.item.storyItem.id { + self.displayViewList = false + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + } else { + self.animateNextNavigationId = id + component.navigate(.id(id)) + } + + break + } + } } else if let captionItem = self.captionItem, captionItem.externalState.isExpanded { if let captionItemView = captionItem.view.view as? StoryContentCaptionComponent.View { captionItemView.collapse(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) @@ -515,7 +591,118 @@ public final class StoryItemSetContainerComponent: Component { if let inputView = self.inputPanel.view, let inputViewHitTest = inputView.hitTest(self.convert(point, to: inputView), with: event) { return inputViewHitTest } - return super.hitTest(point, with: event) + guard let result = super.hitTest(point, with: event) else { + return nil + } + if result === self.scroller { + return self.itemsContainerView + } + return result + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.scrollingOffsetX = scrollView.contentOffset.x + + self.adjustScroller() + self.updateScrolling(transition: .immediate) + } + } + + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + self.snapScrolling() + } + } + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.snapScrolling() + } + + private func snapScrolling() { + self.scroller.setContentOffset(CGPoint(x: self.scrollingCenterX, y: 0.0), animated: true) + } + + private func adjustScroller() { + guard let component = self.component, let itemLayout = self.itemLayout else { + return + } + + self.ignoreScrolling = true + + self.scroller.isScrollEnabled = self.displayViewList + + let itemSpacing: CGFloat = 12.0 + let centralVisibleItemWidth = itemLayout.contentFrame.width * itemLayout.contentVisualScale + let sideVisibleItemWidth = centralVisibleItemWidth - 30.0 + let fullItemScrollDistance = centralVisibleItemWidth * 0.5 + itemSpacing + sideVisibleItemWidth * 0.5 + + var additionalInitializationDistance: CGFloat = 0.0 + if let (switchFromId, switchToId) = self.awaitingSwitchToId { + if component.slice.item.storyItem.id == switchToId { + self.awaitingSwitchToId = nil + + if let previousIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == switchFromId }), let centralIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == switchToId }) { + let fractionDistance = CGFloat(previousIndex - centralIndex) + + let currentOffset = self.scrollingCenterX - self.scrollingOffsetX + additionalInitializationDistance = -(currentOffset - fractionDistance * fullItemScrollDistance) + } + + self.initializedOffset = false + } else { + self.ignoreScrolling = false + return + } + } + + if let centralIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) { + var leftWidth: CGFloat = 0.0 + var rightWidth: CGFloat = 0.0 + if centralIndex != 0 { + leftWidth = 600.0 + } + if centralIndex != component.slice.allItems.count - 1 { + rightWidth = 600.0 + } + + self.scrollingCenterX = leftWidth + self.scroller.contentSize = CGSize(width: leftWidth + itemLayout.size.width + rightWidth, height: 1.0) + + if !self.initializedOffset { + self.initializedOffset = true + self.scrollingOffsetX = leftWidth + additionalInitializationDistance + self.scroller.contentOffset = CGPoint(x: self.scrollingOffsetX, y: 0.0) + } + + var lowestFraction: (Int, CGFloat)? + + for index in 0 ..< component.slice.allItems.count { + let offsetFraction: CGFloat = (self.scrollingCenterX - self.scrollingOffsetX) / fullItemScrollDistance + let centerFraction: CGFloat = CGFloat(index - centralIndex) + + let combinedFraction = abs(offsetFraction + centerFraction) + + if let (_, lowestValue) = lowestFraction { + if combinedFraction < lowestValue { + lowestFraction = (index, combinedFraction) + } + } else { + lowestFraction = (index, combinedFraction) + } + } + + if let (index, _) = lowestFraction, index != centralIndex { + let fixedId = component.slice.allItems[index].storyItem.id + component.navigate(.id(fixedId)) + self.awaitingSwitchToId = (component.slice.item.storyItem.id, fixedId) + } + } + + self.ignoreScrolling = false } private func isProgressPaused() -> Bool { @@ -563,76 +750,178 @@ public final class StoryItemSetContainerComponent: Component { return } - var validIds: [AnyHashable] = [] - let focusedItem = component.slice.item + var validIds: [Int32] = [] + var trulyValidIds: [Int32] = [] - validIds.append(focusedItem.id) - - 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 centralItemFrame = itemLayout.contentFrame.center.offsetBy(dx: 0.0, dy: 0.0) - let itemEnvironment = StoryContentItem.Environment( - externalState: visibleItem.externalState, - sharedState: component.storyItemSharedState, - theme: component.theme, - presentationProgressUpdated: { [weak self, weak visibleItem] progress, canSwitch in - guard let self = self, let component = self.component else { - return - } - guard let visibleItem else { - return - } - visibleItem.currentProgress = progress + let centralVisibleItemWidth = itemLayout.contentFrame.width * itemLayout.contentVisualScale + let sideVisibleItemWidth = centralVisibleItemWidth - 30.0 + let sideVisibleItemScale = itemLayout.contentVisualScale * (sideVisibleItemWidth / centralVisibleItemWidth) + + let itemSpacing: CGFloat = 12.0 + + let fullItemScrollDistance = centralVisibleItemWidth * 0.5 + itemSpacing + sideVisibleItemWidth * 0.5 + let halfItemScrollDistance = sideVisibleItemWidth * 0.5 + itemSpacing + sideVisibleItemWidth * 0.5 + + if let centralIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) { + for index in 0 ..< component.slice.allItems.count { + let item = component.slice.allItems[index] - if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View { - navigationStripView.updateCurrentItemProgress(value: progress, transition: .immediate) + let offsetFraction: CGFloat = (self.scrollingCenterX - self.scrollingOffsetX) / fullItemScrollDistance + let centerIndexOffset = index - centralIndex + let centerFraction: CGFloat = CGFloat(centerIndexOffset) + + let combinedFraction = offsetFraction + centerFraction + let combinedFractionSign: CGFloat = combinedFraction < 0.0 ? -1.0 : 1.0 + + let fractionDistanceToCenter: CGFloat = min(1.0, abs(combinedFraction)) + + var itemPosition = centralItemFrame + itemPosition.x += min(1.0, abs(combinedFraction)) * combinedFractionSign * fullItemScrollDistance + itemPosition.x += max(0.0, abs(combinedFraction) - 1.0) * combinedFractionSign * halfItemScrollDistance + + var itemVisible = true + if abs(centerIndexOffset) > 2 { + itemVisible = false } - if progress >= 1.0 && canSwitch && !visibleItem.requestedNext { - visibleItem.requestedNext = true + if itemLayout.contentVisualScale >= 1.0 - 0.001 && !self.preparingToDisplayViewList { + if index != centralIndex { + itemVisible = false + } + } + var reevaluateVisibilityOnCompletion = false + if !itemVisible { + if transition.animation.isImmediate { + continue + } else { + if self.visibleItems[item.storyItem.id] == nil { + continue + } else { + reevaluateVisibilityOnCompletion = true + } + } + } + + let scaleFraction: CGFloat = abs(max(-1.0, min(1.0, combinedFraction))) + let itemScale = itemLayout.contentVisualScale * (1.0 - scaleFraction) + sideVisibleItemScale * scaleFraction + + validIds.append(item.storyItem.id) + if itemVisible { + trulyValidIds.append(item.storyItem.id) + } + + var itemTransition = transition + let visibleItem: VisibleItem + if let current = self.visibleItems[item.storyItem.id] { + visibleItem = current + } else { + itemTransition = .immediate + visibleItem = VisibleItem() + self.visibleItems[item.storyItem.id] = visibleItem + } + + let itemEnvironment = StoryContentItem.Environment( + externalState: visibleItem.externalState, + sharedState: component.storyItemSharedState, + theme: component.theme, + presentationProgressUpdated: { [weak self, weak visibleItem] progress, canSwitch in + guard let self = self, let component = self.component 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 && canSwitch && !visibleItem.requestedNext { + visibleItem.requestedNext = true + + component.navigate(.next) + } + }, + markAsSeen: { [weak self] id in + guard let self, let component = self.component else { + return + } + component.markAsSeen(id) + } + ) + let _ = visibleItem.view.update( + transition: itemTransition.withUserData(StoryItemContentComponent.Hint( + synchronousLoad: index == centralIndex + )), + component: AnyComponent(StoryItemContentComponent( + context: component.context, + peer: component.slice.peer, + item: item.storyItem + )), + environment: { + itemEnvironment + }, + containerSize: itemLayout.size + ) + if let view = visibleItem.view.view { + if visibleItem.contentContainerView.superview == nil { + self.itemsContainerView.addSubview(visibleItem.contentContainerView) + visibleItem.contentContainerView.addSubview(view) + } - component.navigate(.next) + itemTransition.setPosition(view: view, position: CGPoint(x: itemLayout.contentFrame.size.width * 0.5, y: itemLayout.contentFrame.size.height * 0.5)) + itemTransition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size)) + + let itemId = item.storyItem.id + itemTransition.setPosition(view: visibleItem.contentContainerView, position: itemPosition, completion: { [weak self] _ in + guard reevaluateVisibilityOnCompletion, let self else { + return + } + if !self.trulyValidIds.contains(itemId), let visibleItem = self.visibleItems[itemId] { + self.visibleItems.removeValue(forKey: itemId) + visibleItem.contentContainerView.removeFromSuperview() + } + }) + itemTransition.setBounds(view: visibleItem.contentContainerView, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size)) + + var transform = CATransform3DMakeScale(itemScale, itemScale, 1.0) + if let pinchState = component.pinchState { + let pinchOffset = CGPoint( + x: pinchState.location.x - itemLayout.contentFrame.width / 2.0, + y: pinchState.location.y - itemLayout.contentFrame.height / 2.0 + ) + transform = CATransform3DTranslate( + transform, + pinchState.offset.x - pinchOffset.x * (pinchState.scale - 1.0), + pinchState.offset.y - pinchOffset.y * (pinchState.scale - 1.0), + 0.0 + ) + transform = CATransform3DScale(transform, pinchState.scale, pinchState.scale, 0.0) + } + itemTransition.setTransform(view: visibleItem.contentContainerView, transform: transform) + itemTransition.setCornerRadius(layer: visibleItem.contentContainerView.layer, cornerRadius: 12.0 * (1.0 / itemScale)) + itemTransition.setAlpha(view: visibleItem.contentContainerView, alpha: 1.0 * (1.0 - fractionDistanceToCenter) + 0.75 * fractionDistanceToCenter) + + var itemProgressPaused = self.isProgressPaused() + if index != centralIndex { + itemProgressPaused = true + } + + if let view = view as? StoryContentItem.View { + view.setIsProgressPaused(itemProgressPaused) + } } - }, - markAsSeen: { [weak self] id in - guard let self, let component = self.component else { - return - } - component.markAsSeen(id) - } - ) - let _ = visibleItem.view.update( - transition: itemTransition, - component: focusedItem.component, - environment: { - itemEnvironment - }, - containerSize: itemLayout.size - ) - if let view = visibleItem.view.view { - if view.superview == nil { - 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.isProgressPaused()) } } - var removeIds: [AnyHashable] = [] + self.trulyValidIds = trulyValidIds + + var removeIds: [Int32] = [] for (id, visibleItem) in self.visibleItems { if !validIds.contains(id) { removeIds.append(id) - if let view = visibleItem.view.view { - view.removeFromSuperview() - } + visibleItem.contentContainerView.removeFromSuperview() } } for id in removeIds { @@ -641,29 +930,43 @@ public final class StoryItemSetContainerComponent: Component { } func updateIsProgressPaused() { - for (_, visibleItem) in self.visibleItems { + let isProgressPaused = self.isProgressPaused() + var centralId: Int32? + if let component = self.component { + centralId = component.slice.item.storyItem.id + } + + for (id, visibleItem) in self.visibleItems { if let view = visibleItem.view.view { if let view = view as? StoryContentItem.View { - view.setIsProgressPaused(self.isProgressPaused()) + view.setIsProgressPaused(isProgressPaused || id != centralId) } } } } - func activateInput() { + func activateInput() -> Bool { guard let component = self.component else { - return + return false } if component.slice.peer.id == component.context.account.peerId { if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty { self.displayViewList = true + if component.verticalPanFraction == 0.0 { + self.preparingToDisplayViewList = true + self.updateScrolling(transition: .immediate) + self.preparingToDisplayViewList = false + } self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + return true } } else { if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View { inputPanelView.activateInput() + return false } } + return false } func animateIn(transitionIn: StoryContainerScreen.TransitionIn) { @@ -681,7 +984,7 @@ public final class StoryItemSetContainerComponent: Component { } if let viewListView = self.viewList?.view.view { viewListView.layer.animatePosition( - from: CGPoint(x: 0.0, y: self.bounds.height - self.contentContainerView.frame.maxY), + from: CGPoint(x: 0.0, y: self.bounds.height - self.controlsContainerView.frame.maxY), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, @@ -700,9 +1003,9 @@ public final class StoryItemSetContainerComponent: Component { captionItemView.layer.animateAlpha(from: 0.0, to: captionItemView.alpha, duration: 0.28) } - if let sourceView = transitionIn.sourceView { + if let component = self.component, let sourceView = transitionIn.sourceView, let contentContainerView = self.visibleItems[component.slice.item.storyItem.id]?.contentContainerView { let sourceLocalFrame = sourceView.convert(transitionIn.sourceRect, to: self) - let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - self.contentContainerView.frame.minX, y: sourceLocalFrame.minY - self.contentContainerView.frame.minY), size: sourceLocalFrame.size) + let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - contentContainerView.frame.minX, y: sourceLocalFrame.minY - contentContainerView.frame.minY), size: sourceLocalFrame.size) if let centerInfoView = self.centerInfoItem?.view.view { centerInfoView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) @@ -719,17 +1022,27 @@ public final class StoryItemSetContainerComponent: Component { } } - self.contentContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.contentContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.contentContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: self.contentContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.contentContainerView.layer.animate( + contentContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: contentContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + contentContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: contentContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + contentContainerView.layer.animate( from: transitionIn.sourceCornerRadius as NSNumber, - to: self.contentContainerView.layer.cornerRadius as NSNumber, + to: contentContainerView.layer.cornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3 ) - if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view { + self.controlsContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.controlsContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + self.controlsContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: self.controlsContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + self.controlsContainerView.layer.animate( + from: transitionIn.sourceCornerRadius as NSNumber, + to: self.controlsContainerView.layer.cornerRadius as NSNumber, + keyPath: "cornerRadius", + timingFunction: kCAMediaTimingFunctionSpring, + duration: 0.3 + ) + + if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.storyItem.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)) @@ -777,7 +1090,7 @@ public final class StoryItemSetContainerComponent: Component { if let viewListView = self.viewList?.view.view { viewListView.layer.animatePosition( from: CGPoint(), - to: CGPoint(x: 0.0, y: self.bounds.height - self.contentContainerView.frame.maxY), + to: CGPoint(x: 0.0, y: self.bounds.height - self.controlsContainerView.frame.maxY), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, @@ -797,11 +1110,11 @@ public final class StoryItemSetContainerComponent: Component { captionItemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) } - if let sourceView = transitionOut.destinationView { + if let component = self.component, let sourceView = transitionOut.destinationView, let contentContainerView = self.visibleItems[component.slice.item.storyItem.id]?.contentContainerView { let sourceLocalFrame = sourceView.convert(transitionOut.destinationRect, to: self) - let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - self.contentContainerView.frame.minX, y: sourceLocalFrame.minY - self.contentContainerView.frame.minY), size: sourceLocalFrame.size) + let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - contentContainerView.frame.minX, y: sourceLocalFrame.minY - contentContainerView.frame.minY), size: sourceLocalFrame.size) - let contentSourceFrame = self.contentContainerView.frame + let contentSourceFrame = contentContainerView.frame if let centerInfoView = self.centerInfoItem?.view.view { centerInfoView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) @@ -818,7 +1131,7 @@ public final class StoryItemSetContainerComponent: Component { let transitionSourceContainerView = UIView(frame: self.bounds) transitionSourceContainerView.isUserInteractionEnabled = false - self.insertSubview(transitionSourceContainerView, aboveSubview: self.contentContainerView) + self.insertSubview(transitionSourceContainerView, aboveSubview: self.itemsContainerView) transitionSourceContainerView.addSubview(transitionViewImpl) @@ -891,10 +1204,21 @@ public final class StoryItemSetContainerComponent: Component { } } - self.contentContainerView.layer.animatePosition(from: self.contentContainerView.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - self.contentContainerView.layer.animateBounds(from: self.contentContainerView.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - self.contentContainerView.layer.animate( - from: self.contentContainerView.layer.cornerRadius as NSNumber, + contentContainerView.layer.animatePosition(from: contentContainerView.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + contentContainerView.layer.animateBounds(from: contentContainerView.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + contentContainerView.layer.animate( + from: contentContainerView.layer.cornerRadius as NSNumber, + to: transitionOut.destinationCornerRadius as NSNumber, + keyPath: "cornerRadius", + timingFunction: kCAMediaTimingFunctionSpring, + duration: 0.3, + removeOnCompletion: false + ) + + self.controlsContainerView.layer.animatePosition(from: self.controlsContainerView.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.controlsContainerView.layer.animateBounds(from: self.controlsContainerView.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.controlsContainerView.layer.animate( + from: self.controlsContainerView.layer.cornerRadius as NSNumber, to: transitionOut.destinationCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: kCAMediaTimingFunctionSpring, @@ -912,7 +1236,7 @@ public final class StoryItemSetContainerComponent: Component { let transitionSourceContainerView = UIView(frame: self.bounds) transitionSourceContainerView.isUserInteractionEnabled = false - self.insertSubview(transitionSourceContainerView, belowSubview: self.contentContainerView) + self.insertSubview(transitionSourceContainerView, belowSubview: self.itemsContainerView) transitionSourceContainerView.addSubview(transitionViewImpl) @@ -954,7 +1278,8 @@ public final class StoryItemSetContainerComponent: Component { transitionViewImpl.alpha = 1.0 transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) } - self.contentContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + contentContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.controlsContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) for transitionViewImpl in transitionViewsImpl { transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) @@ -967,7 +1292,7 @@ public final class StoryItemSetContainerComponent: Component { } } - if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view { + if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.storyItem.id]?.view.view { let innerScale = innerSourceLocalFrame.width / visibleItemView.bounds.width var adjustedInnerSourceLocalFrame = innerSourceLocalFrame @@ -1031,6 +1356,12 @@ public final class StoryItemSetContainerComponent: Component { if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id { component.markAsSeen(StoryId(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id)) + self.initializedOffset = false + } + var itemsTransition = transition + if let animateNextNavigationId = self.animateNextNavigationId, animateNextNavigationId == component.slice.item.storyItem.id { + self.animateNextNavigationId = nil + itemsTransition = transition.withAnimation(.curve(duration: 0.3, curve: .spring)) } if self.topContentGradientLayer.colors == nil { @@ -1074,8 +1405,6 @@ public final class StoryItemSetContainerComponent: Component { self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3) } - //self.updatePreloads() - let wasPanning = self.component?.isPanning ?? false self.component = component self.state = state @@ -1416,7 +1745,9 @@ public final class StoryItemSetContainerComponent: Component { viewList.view.parentState = state let viewListSize = viewList.view.update( - transition: viewListTransition, + transition: viewListTransition.withUserData(PeerListItemComponent.TransitionHint( + synchronousLoad: false + )), component: AnyComponent(StoryItemSetViewListComponent( externalState: viewList.externalState, context: component.context, @@ -1439,6 +1770,11 @@ public final class StoryItemSetContainerComponent: Component { if !self.displayViewList { self.displayViewList = true + + self.preparingToDisplayViewList = true + self.updateScrolling(transition: .immediate) + self.preparingToDisplayViewList = false + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) } }, @@ -1711,8 +2047,17 @@ public final class StoryItemSetContainerComponent: Component { let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: component.containerInsets.top - (contentSize.height - contentVisualHeight) * 0.5), size: contentSize) - transition.setPosition(view: self.contentContainerView, position: contentFrame.center) - transition.setBounds(view: self.contentContainerView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size)) + let itemLayout = ItemLayout( + size: availableSize, + contentFrame: contentFrame, + contentVisualScale: contentVisualScale + ) + self.itemLayout = itemLayout + + transition.setFrame(view: self.itemsContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: component.containerInsets.top + floor(contentVisualHeight)))) + + transition.setPosition(view: self.controlsContainerView, position: contentFrame.center) + transition.setBounds(view: self.controlsContainerView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size)) var transform = CATransform3DMakeScale(contentVisualScale, contentVisualScale, 1.0) if let pinchState = component.pinchState { @@ -1728,10 +2073,9 @@ public final class StoryItemSetContainerComponent: Component { ) transform = CATransform3DScale(transform, pinchState.scale, pinchState.scale, 0.0) } - transition.setTransform(view: self.contentContainerView, transform: transform) + transition.setTransform(view: self.controlsContainerView, transform: transform) - //transition.setScale(view: self.contentContainerView, scale: contentVisualScale) - transition.setCornerRadius(layer: self.contentContainerView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale)) + transition.setCornerRadius(layer: self.controlsContainerView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale)) if self.closeButtonIconView.image == nil { self.closeButtonIconView.image = UIImage(bundleImageName: "Media Gallery/Close")?.withRenderingMode(.alwaysTemplate) @@ -1741,9 +2085,10 @@ public final class StoryItemSetContainerComponent: Component { let closeButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 50.0, height: 64.0)) transition.setFrame(view: self.closeButton, frame: closeButtonFrame) transition.setFrame(view: self.closeButtonIconView, frame: CGRect(origin: CGPoint(x: floor((closeButtonFrame.width - image.size.width) * 0.5), y: floor((closeButtonFrame.height - image.size.height) * 0.5)), size: image.size)) - transition.setAlpha(view: self.closeButton, alpha: (component.hideUI || self.displayViewList || self.isEditingStory) ? 0.0 : 1.0) } + transition.setAlpha(view: self.controlsContainerView, alpha: (component.hideUI || self.isEditingStory || self.displayViewList) ? 0.0 : 1.0) + let focusedItem: StoryContentItem? = component.slice.item let _ = focusedItem /*if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) { @@ -1751,13 +2096,12 @@ public final class StoryItemSetContainerComponent: Component { }*/ var currentRightInfoItem: InfoItem? - if let focusedItem { - if let rightInfoComponent = focusedItem.rightInfoComponent { - if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == focusedItem.rightInfoComponent { - currentRightInfoItem = rightInfoItem - } else { - currentRightInfoItem = InfoItem(component: rightInfoComponent) - } + if focusedItem != nil { + let rightInfoComponent = AnyComponent(StoryAvatarInfoComponent(context: component.context, peer: component.slice.peer)) + if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == rightInfoComponent { + currentRightInfoItem = rightInfoItem + } else { + currentRightInfoItem = InfoItem(component: rightInfoComponent) } } @@ -1772,13 +2116,12 @@ public final class StoryItemSetContainerComponent: Component { } var currentCenterInfoItem: InfoItem? - if let focusedItem { - if let centerInfoComponent = focusedItem.centerInfoComponent { - if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == focusedItem.centerInfoComponent { - currentCenterInfoItem = centerInfoItem - } else { - currentCenterInfoItem = InfoItem(component: centerInfoComponent) - } + if focusedItem != nil { + let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent(context: component.context, peer: component.slice.peer, timestamp: component.slice.item.storyItem.timestamp)) + if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == centerInfoComponent { + currentCenterInfoItem = centerInfoItem + } else { + currentCenterInfoItem = InfoItem(component: centerInfoComponent) } } @@ -1806,7 +2149,7 @@ public final class StoryItemSetContainerComponent: Component { if let view = currentCenterInfoItem.view.view { var animateIn = false if view.superview == nil { - self.contentContainerView.insertSubview(view, belowSubview: self.closeButton) + self.controlsContainerView.insertSubview(view, belowSubview: self.closeButton) animateIn = true } transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: centerInfoItemSize)) @@ -1815,7 +2158,7 @@ public final class StoryItemSetContainerComponent: Component { //view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - transition.setAlpha(view: view, alpha: (component.hideUI || self.displayViewList || self.isEditingStory) ? 0.0 : 1.0) + transition.setAlpha(view: view, alpha: self.isEditingStory ? 0.0 : 1.0) } } @@ -1836,7 +2179,7 @@ public final class StoryItemSetContainerComponent: Component { if let view = currentRightInfoItem.view.view { var animateIn = false if view.superview == nil { - self.contentContainerView.addSubview(view) + self.controlsContainerView.addSubview(view) animateIn = true } transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: contentFrame.width - 6.0 - rightInfoItemSize.width, y: 14.0), size: rightInfoItemSize)) @@ -1846,7 +2189,7 @@ public final class StoryItemSetContainerComponent: Component { view.layer.animateScale(from: 0.5, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) } - transition.setAlpha(view: view, alpha: (component.hideUI || self.displayViewList || self.isEditingStory) ? 0.0 : 1.0) + transition.setAlpha(view: view, alpha: self.isEditingStory ? 0.0 : 1.0) } } @@ -1901,7 +2244,7 @@ public final class StoryItemSetContainerComponent: Component { let closeFriendIconFrame = CGRect(origin: CGPoint(x: contentFrame.width - 6.0 - 52.0 - closeFriendIconSize.width, y: 21.0), size: closeFriendIconSize) if let closeFriendIconView = closeFriendIcon.view { if closeFriendIconView.superview == nil { - self.contentContainerView.addSubview(closeFriendIconView) + self.controlsContainerView.addSubview(closeFriendIconView) closeFriendIconTransition.setFrame(view: closeFriendIconView, frame: closeFriendIconFrame) } } @@ -1914,12 +2257,8 @@ public final class StoryItemSetContainerComponent: Component { transition.setFrame(layer: self.topContentGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: gradientHeight))) transition.setAlpha(layer: self.topContentGradientLayer, alpha: (component.hideUI || self.displayViewList || self.isEditingStory) ? 0.0 : 1.0) - let itemSize = CGSize(width: contentFrame.width, height: floorToScreenPixels(contentFrame.width * 1.77778)) - let itemLayout = ItemLayout(size: itemSize) //ItemLayout(size: CGSize(width: contentFrame.width, height: availableSize.height - component.containerInsets.top - 44.0 - bottomContentInsetWithoutInput)) - self.itemLayout = itemLayout - let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize) - var inputPanelAlpha: CGFloat = focusedItem?.isMy == true || component.hideUI || self.isEditingStory ? 0.0 : 1.0 + var inputPanelAlpha: CGFloat = component.slice.peer.id == component.context.account.peerId || component.hideUI || self.isEditingStory ? 0.0 : 1.0 if case .regular = component.metrics.widthClass { inputPanelAlpha *= component.visibilityFraction } @@ -1929,7 +2268,7 @@ public final class StoryItemSetContainerComponent: Component { } var inputPanelOffset: CGFloat = 0.0 - if focusedItem?.isMy == false && !self.inputPanelExternalState.isEditing { + if component.slice.peer.id != component.context.account.peerId && !self.inputPanelExternalState.isEditing { let bandingOffset = scrollingRubberBandingOffset(offset: component.verticalPanFraction * availableSize.height, bandingStart: 0.0, range: 10.0) inputPanelOffset = -max(0.0, min(10.0, bandingOffset)) } @@ -2002,7 +2341,7 @@ public final class StoryItemSetContainerComponent: Component { let captionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.height - captionSize.height), size: captionSize) if let captionItemView = captionItem.view.view { if captionItemView.superview == nil { - self.contentContainerView.insertSubview(captionItemView, aboveSubview: self.contentDimView) + self.controlsContainerView.insertSubview(captionItemView, aboveSubview: self.contentDimView) } captionItemTransition.setFrame(view: captionItemView, frame: captionFrame) captionItemTransition.setAlpha(view: captionItemView, alpha: (component.hideUI || self.displayViewList || self.isEditingStory || self.inputPanelExternalState.isEditing) ? 0.0 : 1.0) @@ -2272,7 +2611,12 @@ public final class StoryItemSetContainerComponent: Component { transition.setAlpha(view: self.contentDimView, alpha: dimAlpha) } - self.updateScrolling(transition: transition) + self.ignoreScrolling = true + transition.setFrame(view: self.scroller, frame: CGRect(origin: CGPoint(), size: availableSize)) + self.ignoreScrolling = false + + self.adjustScroller() + self.updateScrolling(transition: itemsTransition) if let focusedItem, let visibleItem = self.visibleItems[focusedItem.storyItem.id], let index = focusedItem.position { let navigationStripSideInset: CGFloat = 8.0 @@ -2294,15 +2638,15 @@ public final class StoryItemSetContainerComponent: Component { if let navigationStripView = self.navigationStrip.view { if navigationStripView.superview == nil { navigationStripView.isUserInteractionEnabled = false - self.contentContainerView.addSubview(navigationStripView) + self.controlsContainerView.addSubview(navigationStripView) } transition.setFrame(view: navigationStripView, frame: CGRect(origin: CGPoint(x: navigationStripSideInset, y: navigationStripTopInset), size: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0))) - transition.setAlpha(view: navigationStripView, alpha: (component.hideUI || self.displayViewList || self.isEditingStory) ? 0.0 : 1.0) + transition.setAlpha(view: navigationStripView, alpha: self.isEditingStory ? 0.0 : 1.0) } } component.externalState.derivedMediaSize = contentFrame.size - if focusedItem?.isMy == true { + if component.slice.peer.id == component.context.account.peerId { component.externalState.derivedBottomInset = availableSize.height - contentFrame.maxY } else { component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrame.minY, contentFrame.maxY) diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryPositionInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryPositionInfoComponent.swift similarity index 100% rename from submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryPositionInfoComponent.swift rename to submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryPositionInfoComponent.swift diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryVideoDecoration.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryVideoDecoration.swift similarity index 100% rename from submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryVideoDecoration.swift rename to submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryVideoDecoration.swift diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD b/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD deleted file mode 100644 index 0745d5d430..0000000000 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "StoryContentComponent", - module_name = "StoryContentComponent", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ], - deps = [ - "//submodules/Display", - "//submodules/AsyncDisplayKit", - "//submodules/ComponentFlow", - "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", - "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/AccountContext", - "//submodules/TelegramCore", - "//submodules/Postbox", - "//submodules/PhotoResources", - "//submodules/MediaPlayer:UniversalMediaPlayer", - "//submodules/TelegramUniversalVideoContent", - "//submodules/AvatarNode", - "//submodules/Components/HierarchyTrackingLayer", - "//submodules/TelegramUI/Components/ButtonComponent", - "//submodules/Components/MultilineTextComponent", - "//submodules/TelegramPresentationData", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index fb00452f13..a9b7c5dbda 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -95,7 +95,6 @@ import ICloudResources import LegacyCamera import LegacyInstantVideoController import StoryContainerScreen -import StoryContentComponent import MoreHeaderButton import VolumeButtons import ChatAvatarNavigationNode diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 29953348fe..f29ad9dd53 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -23,7 +23,6 @@ import UndoUI import WebsiteType import GalleryData import StoryContainerScreen -import StoryContentComponent func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { var story: TelegramMediaStory? diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index a97f9007b9..94ee68ebb9 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -31,7 +31,6 @@ import PremiumUI import AuthorizationUI import ChatFolderLinkPreviewScreen import StoryContainerScreen -import StoryContentComponent private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer { if case .default = navigation { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index a063708077..3594be4de0 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -89,7 +89,6 @@ import SendInviteLinkScreen import PeerInfoVisualMediaPaneNode import PeerInfoStoryGridScreen import StoryContainerScreen -import StoryContentComponent import ChatAvatarNavigationNode import PeerReportScreen