This commit is contained in:
Ali 2023-06-26 01:58:08 +03:00
parent c302b7d4a5
commit 04b05b510a
27 changed files with 715 additions and 369 deletions

View File

@ -94,7 +94,6 @@ swift_library(
"//submodules/QrCodeUI", "//submodules/QrCodeUI",
"//submodules/TelegramUI/Components/ActionPanelComponent", "//submodules/TelegramUI/Components/ActionPanelComponent",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/Stories/StoryContentComponent",
"//submodules/TelegramUI/Components/Stories/StoryPeerListComponent", "//submodules/TelegramUI/Components/Stories/StoryPeerListComponent",
"//submodules/TelegramUI/Components/FullScreenEffectView", "//submodules/TelegramUI/Components/FullScreenEffectView",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",

View File

@ -46,7 +46,6 @@ import ChatListTitleView
import InviteLinksUI import InviteLinksUI
import ChatFolderLinkPreviewScreen import ChatFolderLinkPreviewScreen
import StoryContainerScreen import StoryContainerScreen
import StoryContentComponent
import FullScreenEffectView import FullScreenEffectView
private final class ContextControllerContentSourceImpl: ContextControllerContentSource { private final class ContextControllerContentSourceImpl: ContextControllerContentSource {

View File

@ -38,7 +38,6 @@ swift_library(
"//submodules/QrCodeUI:QrCodeUI", "//submodules/QrCodeUI:QrCodeUI",
"//submodules/LocalizedPeerData:LocalizedPeerData", "//submodules/LocalizedPeerData:LocalizedPeerData",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/Stories/StoryContentComponent",
"//submodules/TelegramUI/Components/Stories/StoryPeerListComponent", "//submodules/TelegramUI/Components/Stories/StoryPeerListComponent",
"//submodules/TelegramUI/Components/ChatListTitleView", "//submodules/TelegramUI/Components/ChatListTitleView",
"//submodules/TelegramUI/Components/ChatListHeaderComponent", "//submodules/TelegramUI/Components/ChatListHeaderComponent",

View File

@ -20,7 +20,6 @@ import StickerResources
import ContextUI import ContextUI
import QrCodeUI import QrCodeUI
import StoryContainerScreen import StoryContainerScreen
import StoryContentComponent
import ChatListHeaderComponent import ChatListHeaderComponent
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {

View File

@ -25,7 +25,6 @@ swift_library(
"//submodules/MediaResources:MediaResources", "//submodules/MediaResources:MediaResources",
"//submodules/WebsiteType:WebsiteType", "//submodules/WebsiteType:WebsiteType",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/Stories/StoryContentComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -15,7 +15,6 @@ import GalleryUI
import MediaResources import MediaResources
import WebsiteType import WebsiteType
import StoryContainerScreen import StoryContainerScreen
import StoryContentComponent
public enum ChatMessageGalleryControllerData { public enum ChatMessageGalleryControllerData {
case url(String) case url(String)

View File

@ -598,6 +598,21 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
updateNotificationsView({}) updateNotificationsView({})
}) })
}, removePeerFromExceptions: { }, removePeerFromExceptions: {
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 _ = ( let _ = (
context.engine.peers.removeCustomNotificationSettings(peerIds: [peerId]) context.engine.peers.removeCustomNotificationSettings(peerIds: [peerId])
|> map { _ -> EnginePeer? in } |> map { _ -> EnginePeer? in }
@ -611,6 +626,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
} }
updateNotificationsView({}) updateNotificationsView({})
}) })
}
}, modifiedPeer: { }, modifiedPeer: {
})) }))
@ -699,6 +715,23 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
let values = stateValue.with { $0.mode.settings.values } let values = stateValue.with { $0.mode.settings.values }
if case .stories = mode.mode {
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.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 }) let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { $0.peer })
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
updateNotificationsDisposable.set(nil) updateNotificationsDisposable.set(nil)
@ -714,6 +747,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
updateNotificationsView({}) updateNotificationsView({})
}) })
}) })
}
}) })
]), ActionSheetItemGroup(items: [ ]), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
@ -726,6 +760,19 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
return current.withUpdatedRevealedPeerId(peerId) return current.withUpdatedRevealedPeerId(peerId)
} }
}, removePeer: { peer in }, removePeer: { peer in
if case .stories = mode.mode {
let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [peer])
|> deliverOnMainQueue).start(completed: {
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]) let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [peer])
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
updateNotificationsDisposable.set(nil) updateNotificationsDisposable.set(nil)
@ -737,6 +784,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
updateNotificationsView({}) updateNotificationsView({})
}) })
}) })
}
}, updatedExceptionMode: { mode in }, updatedExceptionMode: { mode in
_ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels, stories) in _ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels, stories) in
switch mode { switch mode {

View File

@ -1103,7 +1103,7 @@ func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32, asPi
account.stateManager.injectStoryUpdates(updates: [.read(peerId: peerId, maxId: id)]) account.stateManager.injectStoryUpdates(updates: [.read(peerId: peerId, maxId: id)])
#if DEBUG && true #if DEBUG && false
if "".isEmpty { if "".isEmpty {
return .complete() return .complete()
} }

View File

@ -303,6 +303,15 @@ public extension TelegramEngine {
|> ignoreValues |> ignoreValues
} }
public func removeCustomStoryNotificationSettings(peerIds: [PeerId]) -> Signal<Never, NoError> {
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 { public func channelAdminEventLog(peerId: PeerId) -> ChannelAdminEventLogContext {
return ChannelAdminEventLogContext(postbox: self.account.postbox, network: self.account.network, peerId: peerId) return ChannelAdminEventLogContext(postbox: self.account.postbox, network: self.account.network, peerId: peerId)
} }

View File

@ -359,7 +359,6 @@ swift_library(
"//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen", "//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen",
"//submodules/TelegramUI/Components/SliderContextItem:SliderContextItem", "//submodules/TelegramUI/Components/SliderContextItem:SliderContextItem",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/Stories/StoryContentComponent",
"//submodules/TelegramUI/Components/CameraScreen", "//submodules/TelegramUI/Components/CameraScreen",
"//submodules/TelegramUI/Components/MediaEditorScreen", "//submodules/TelegramUI/Components/MediaEditorScreen",
"//submodules/TelegramUI/Components/ChatScheduleTimeController", "//submodules/TelegramUI/Components/ChatScheduleTimeController",

View File

@ -249,15 +249,10 @@ public enum NotificationExceptionMode : Equatable {
if let value = values[peerId] { if let value = values[peerId] {
switch storyNotifications { switch storyNotifications {
case .default: case .default:
switch value.settings.storiesMuted {
case .none:
values.removeValue(forKey: peerId) values.removeValue(forKey: peerId)
default: default:
values[peerId] = value.updateSettings({$0.withUpdatedStoriesMuted(storiesMuted)}).withUpdatedDate(Date().timeIntervalSince1970) values[peerId] = value.updateSettings({$0.withUpdatedStoriesMuted(storiesMuted)}).withUpdatedDate(Date().timeIntervalSince1970)
} }
default:
values[peerId] = value.updateSettings({$0.withUpdatedStoriesMuted(storiesMuted)}).withUpdatedDate(Date().timeIntervalSince1970)
}
} else { } else {
switch storyNotifications { switch storyNotifications {
case .default: case .default:

View File

@ -36,7 +36,6 @@ swift_library(
"//submodules/InvisibleInkDustNode", "//submodules/InvisibleInkDustNode",
"//submodules/MediaPickerUI", "//submodules/MediaPickerUI",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/Stories/StoryContentComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -26,7 +26,6 @@ import AppBundle
import InvisibleInkDustNode import InvisibleInkDustNode
import MediaPickerUI import MediaPickerUI
import StoryContainerScreen import StoryContainerScreen
import StoryContentComponent
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
private let mediaBadgeTextColor = UIColor.white private let mediaBadgeTextColor = UIColor.white

View File

@ -6,7 +6,6 @@ import SwiftSignalKit
import AccountContext import AccountContext
import TelegramCore import TelegramCore
import Postbox import Postbox
import StoryContainerScreen
private struct StoryKey: Hashable { private struct StoryKey: Hashable {
var peerId: EnginePeer.Id 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.nextItems = nextItems
self.sliceValue = StoryContentContextState.FocusedSlice( self.sliceValue = StoryContentContextState.FocusedSlice(
peer: peer, peer: peer,
additionalPeerData: additionalPeerData, additionalPeerData: additionalPeerData,
item: StoryContentItem( item: StoryContentItem(
id: AnyHashable(mappedItem.id),
position: focusedIndex, 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, peerId: peer.id,
storyItem: mappedItem, storyItem: mappedItem
isMy: peerId == context.account.peerId
), ),
totalCount: mappedItems.count, totalCount: mappedItems.count,
previousItemId: previousItemId, previousItemId: previousItemId,
nextItemId: nextItemId nextItemId: nextItemId,
allItems: allItems
) )
self.isReady = true self.isReady = true
self.updated.set(.single(Void())) self.updated.set(.single(Void()))
@ -849,6 +841,10 @@ public final class StoryContentContextImpl: StoryContentContext {
if let nextItemId = slice.nextItemId { if let nextItemId = slice.nextItemId {
currentState.centralPeerContext.currentFocusedId = 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 isCloseFriends: itemValue.isCloseFriends
) )
let mainItem = StoryContentItem(
position: 0,
peerId: peer.id,
storyItem: mappedItem
)
let stateValue = StoryContentContextState( let stateValue = StoryContentContextState(
slice: StoryContentContextState.FocusedSlice( slice: StoryContentContextState.FocusedSlice(
peer: peer, peer: peer,
additionalPeerData: additionalPeerData, additionalPeerData: additionalPeerData,
item: StoryContentItem( item: mainItem,
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
),
totalCount: 1, totalCount: 1,
previousItemId: nil, previousItemId: nil,
nextItemId: nil nextItemId: nil,
allItems: [mainItem]
), ),
previousSlice: nil, previousSlice: nil,
nextSlice: nil nextSlice: nil
@ -1124,34 +1106,27 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
let item = state.items[focusedIndex] let item = state.items[focusedIndex]
self.focusedId = item.id self.focusedId = item.id
let allItems = state.items.map { stateItem -> StoryContentItem in
return StoryContentItem(
position: nil,
peerId: peer.id,
storyItem: stateItem
)
}
stateValue = StoryContentContextState( stateValue = StoryContentContextState(
slice: StoryContentContextState.FocusedSlice( slice: StoryContentContextState.FocusedSlice(
peer: peer, peer: peer,
additionalPeerData: additionalPeerData, additionalPeerData: additionalPeerData,
item: StoryContentItem( item: StoryContentItem(
id: AnyHashable(item.id),
position: nil, 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, peerId: peer.id,
storyItem: item, storyItem: item
isMy: peerId == self.context.account.peerId
), ),
totalCount: state.totalCount, totalCount: state.totalCount,
previousItemId: focusedIndex == 0 ? nil : state.items[focusedIndex - 1].id, 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, previousSlice: nil,
nextSlice: nil nextSlice: nil
@ -1283,15 +1258,19 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
case .peer: case .peer:
break break
case let .item(direction): case let .item(direction):
let indexDifference: Int var indexDifference: Int?
switch direction { switch direction {
case .next: case .next:
indexDifference = 1 indexDifference = 1
case .previous: case .previous:
indexDifference = -1 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 }) { if let index = listState.items.firstIndex(where: { $0.id == focusedId }) {
var nextIndex = index + indexDifference var nextIndex = index + indexDifference
if nextIndex < 0 { if nextIndex < 0 {

View File

@ -75,14 +75,14 @@ private final class StoryContainerScreenComponent: Component {
let content: StoryContentContext let content: StoryContentContext
let focusedItemPromise: Promise<StoryId?> let focusedItemPromise: Promise<StoryId?>
let transitionIn: StoryContainerScreen.TransitionIn? let transitionIn: StoryContainerScreen.TransitionIn?
let transitionOut: (EnginePeer.Id, AnyHashable) -> StoryContainerScreen.TransitionOut? let transitionOut: (EnginePeer.Id, Int32) -> StoryContainerScreen.TransitionOut?
init( init(
context: AccountContext, context: AccountContext,
content: StoryContentContext, content: StoryContentContext,
focusedItemPromise: Promise<StoryId?>, focusedItemPromise: Promise<StoryId?>,
transitionIn: StoryContainerScreen.TransitionIn?, transitionIn: StoryContainerScreen.TransitionIn?,
transitionOut: @escaping (EnginePeer.Id, AnyHashable) -> StoryContainerScreen.TransitionOut? transitionOut: @escaping (EnginePeer.Id, Int32) -> StoryContainerScreen.TransitionOut?
) { ) {
self.context = context self.context = context
self.content = content self.content = content
@ -322,7 +322,7 @@ private final class StoryContainerScreenComponent: Component {
private func commitHorizontalPan(velocity: CGPoint) { private func commitHorizontalPan(velocity: CGPoint) {
if var itemSetPanState = self.itemSetPanState { if var itemSetPanState = self.itemSetPanState {
if let component = self.component, let stateValue = component.content.stateValue, let _ = stateValue.slice { 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 abs(velocity.x) > 10.0 {
if velocity.x < 0.0 { if velocity.x < 0.0 {
if stateValue.nextSlice != nil { if stateValue.nextSlice != nil {
@ -397,17 +397,26 @@ private final class StoryContainerScreenComponent: Component {
let velocity = recognizer.velocity(in: self) let velocity = recognizer.velocity(in: self)
self.verticalPanState = nil 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 { 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() self.environment?.controller()?.dismiss()
} else if translation.y < -100.0 || velocity.y < -40.0 { } 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 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 { 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: default:
break break
} }
@ -532,7 +541,7 @@ private final class StoryContainerScreenComponent: Component {
func animateOut(completion: @escaping () -> Void) { func animateOut(completion: @escaping () -> Void) {
self.isAnimatingOut = true 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) self.state?.updated(transition: .immediate)
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut)) let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
@ -552,7 +561,7 @@ private final class StoryContainerScreenComponent: Component {
focusedItemPromise.set(.single(nil)) focusedItemPromise.set(.single(nil))
}) })
} else { } 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() transitionOut.completed()
} }
@ -828,12 +837,14 @@ private final class StoryContainerScreenComponent: Component {
self.commitHorizontalPan(velocity: CGPoint(x: 100.0, y: 0.0)) self.commitHorizontalPan(velocity: CGPoint(x: 100.0, y: 0.0))
} }
} else { } else {
let mappedDirection: StoryContentContextNavigation.Direction let mappedDirection: StoryContentContextNavigation.ItemDirection
switch direction { switch direction {
case .previous: case .previous:
mappedDirection = .previous mappedDirection = .previous
case .next: case .next:
mappedDirection = .next mappedDirection = .next
case let .id(id):
mappedDirection = .id(id)
} }
component.content.navigate(navigation: .item(mappedDirection)) component.content.navigate(navigation: .item(mappedDirection))
} }

View File

@ -7,7 +7,7 @@ import TelegramCore
import Postbox import Postbox
import TelegramPresentationData import TelegramPresentationData
public final class StoryContentItem { public final class StoryContentItem: Equatable {
public final class ExternalState { public final class ExternalState {
public init() { public init() {
} }
@ -66,61 +66,31 @@ public final class StoryContentItem {
} }
} }
public let id: AnyHashable
public let position: Int? public let position: Int?
public let component: AnyComponent<StoryContentItem.Environment>
public let centerInfoComponent: AnyComponent<Empty>?
public let rightInfoComponent: AnyComponent<Empty>?
public let peerId: EnginePeer.Id? public let peerId: EnginePeer.Id?
public let storyItem: EngineStoryItem public let storyItem: EngineStoryItem
public let isMy: Bool
public init( public init(
id: AnyHashable,
position: Int?, position: Int?,
component: AnyComponent<StoryContentItem.Environment>,
centerInfoComponent: AnyComponent<Empty>?,
rightInfoComponent: AnyComponent<Empty>?,
peerId: EnginePeer.Id?, peerId: EnginePeer.Id?,
storyItem: EngineStoryItem, storyItem: EngineStoryItem
isMy: Bool
) { ) {
self.id = id
self.position = position self.position = position
self.component = component
self.centerInfoComponent = centerInfoComponent
self.rightInfoComponent = rightInfoComponent
self.peerId = peerId self.peerId = peerId
self.storyItem = storyItem self.storyItem = storyItem
self.isMy = isMy
} }
}
public final class StoryContentItemSlice { public static func ==(lhs: StoryContentItem, rhs: StoryContentItem) -> Bool {
public let id: AnyHashable if lhs.position != rhs.position {
public let focusedItemId: AnyHashable? return false
public let items: [StoryContentItem] }
public let totalCount: Int if lhs.peerId != rhs.peerId {
public let previousItemId: AnyHashable? return false
public let nextItemId: AnyHashable? }
public let update: (StoryContentItemSlice, AnyHashable) -> Signal<StoryContentItemSlice, NoError> if lhs.storyItem != rhs.storyItem {
return false
public init( }
id: AnyHashable, return true
focusedItemId: AnyHashable?,
items: [StoryContentItem],
totalCount: Int,
previousItemId: AnyHashable?,
nextItemId: AnyHashable?,
update: @escaping (StoryContentItemSlice, AnyHashable) -> Signal<StoryContentItemSlice, NoError>
) {
self.id = id
self.focusedItemId = focusedItemId
self.items = items
self.totalCount = totalCount
self.previousItemId = previousItemId
self.nextItemId = nextItemId
self.update = update
} }
} }
@ -149,6 +119,7 @@ public final class StoryContentContextState {
public let totalCount: Int public let totalCount: Int
public let previousItemId: Int32? public let previousItemId: Int32?
public let nextItemId: Int32? public let nextItemId: Int32?
public let allItems: [StoryContentItem]
public init( public init(
peer: EnginePeer, peer: EnginePeer,
@ -156,7 +127,8 @@ public final class StoryContentContextState {
item: StoryContentItem, item: StoryContentItem,
totalCount: Int, totalCount: Int,
previousItemId: Int32?, previousItemId: Int32?,
nextItemId: Int32? nextItemId: Int32?,
allItems: [StoryContentItem]
) { ) {
self.peer = peer self.peer = peer
self.additionalPeerData = additionalPeerData self.additionalPeerData = additionalPeerData
@ -164,6 +136,7 @@ public final class StoryContentContextState {
self.totalCount = totalCount self.totalCount = totalCount
self.previousItemId = previousItemId self.previousItemId = previousItemId
self.nextItemId = nextItemId self.nextItemId = nextItemId
self.allItems = allItems
} }
public static func ==(lhs: FocusedSlice, rhs: FocusedSlice) -> Bool { public static func ==(lhs: FocusedSlice, rhs: FocusedSlice) -> Bool {
@ -173,7 +146,7 @@ public final class StoryContentContextState {
if lhs.additionalPeerData != rhs.additionalPeerData { if lhs.additionalPeerData != rhs.additionalPeerData {
return false return false
} }
if lhs.item.storyItem != rhs.item.storyItem { if lhs.item != rhs.item {
return false return false
} }
if lhs.totalCount != rhs.totalCount { if lhs.totalCount != rhs.totalCount {
@ -185,6 +158,9 @@ public final class StoryContentContextState {
if lhs.nextItemId != rhs.nextItemId { if lhs.nextItemId != rhs.nextItemId {
return false return false
} }
if lhs.allItems != rhs.allItems {
return false
}
return true return true
} }
} }
@ -205,13 +181,19 @@ public final class StoryContentContextState {
} }
public enum StoryContentContextNavigation { public enum StoryContentContextNavigation {
public enum Direction { public enum ItemDirection {
case previous
case next
case id(Int32)
}
public enum PeerDirection {
case previous case previous
case next case next
} }
case item(Direction) case item(ItemDirection)
case peer(Direction) case peer(PeerDirection)
} }
public protocol StoryContentContext: AnyObject { public protocol StoryContentContext: AnyObject {

View File

@ -10,7 +10,6 @@ import PhotoResources
import SwiftSignalKit import SwiftSignalKit
import UniversalMediaPlayer import UniversalMediaPlayer
import TelegramUniversalVideoContent import TelegramUniversalVideoContent
import StoryContainerScreen
import HierarchyTrackingLayer import HierarchyTrackingLayer
import ButtonComponent import ButtonComponent
import MultilineTextComponent import MultilineTextComponent
@ -19,6 +18,14 @@ import TelegramPresentationData
final class StoryItemContentComponent: Component { final class StoryItemContentComponent: Component {
typealias EnvironmentType = StoryContentItem.Environment typealias EnvironmentType = StoryContentItem.Environment
final class Hint {
let synchronousLoad: Bool
init(synchronousLoad: Bool) {
self.synchronousLoad = synchronousLoad
}
}
let context: AccountContext let context: AccountContext
let peer: EnginePeer let peer: EnginePeer
let item: EngineStoryItem let item: EngineStoryItem
@ -56,7 +63,7 @@ final class StoryItemContentComponent: Component {
private var unsupportedText: ComponentView<Empty>? private var unsupportedText: ComponentView<Empty>?
private var unsupportedButton: ComponentView<Empty>? private var unsupportedButton: ComponentView<Empty>?
private var isProgressPaused: Bool = false private var isProgressPaused: Bool = true
private var currentProgressTimer: SwiftSignalKit.Timer? private var currentProgressTimer: SwiftSignalKit.Timer?
private var currentProgressTimerValue: Double = 0.0 private var currentProgressTimerValue: Double = 0.0
private var videoProgressDisposable: Disposable? private var videoProgressDisposable: Disposable?
@ -82,7 +89,7 @@ final class StoryItemContentComponent: Component {
guard let self else { guard let self else {
return return
} }
self.updateIsProgressPaused() self.updateIsProgressPaused(update: true)
} }
} }
@ -97,6 +104,17 @@ final class StoryItemContentComponent: Component {
} }
private func performActionAfterImageContentLoaded(update: Bool) { 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 { guard let component = self.component, let environment = self.environment, let currentMessageMedia = self.currentMessageMedia else {
return 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) { override func setIsProgressPaused(_ isProgressPaused: Bool) {
if self.isProgressPaused != isProgressPaused { if self.isProgressPaused != isProgressPaused {
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 { if let videoNode = self.videoNode {
var canPlay = !self.isProgressPaused && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy var canPlay = !self.isProgressPaused && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy
if let component = self.component { if let component = self.component {
@ -192,6 +224,7 @@ final class StoryItemContentComponent: Component {
} }
} }
self.initializeVideoIfReady(update: update)
self.updateVideoPlaybackProgress() self.updateVideoPlaybackProgress()
self.updateProgressTimer() self.updateProgressTimer()
} }
@ -324,6 +357,11 @@ final class StoryItemContentComponent: Component {
let environment = environment[StoryContentItem.Environment.self].value let environment = environment[StoryContentItem.Environment.self].value
self.environment = environment self.environment = environment
var synchronousLoad = false
if let hint = transition.userData(Hint.self) {
synchronousLoad = hint.synchronousLoad
}
let peerReference = PeerReference(component.peer._asPeer()) let peerReference = PeerReference(component.peer._asPeer())
var messageMedia: EngineMedia? var messageMedia: EngineMedia?
@ -353,7 +391,7 @@ final class StoryItemContentComponent: Component {
postbox: component.context.account.postbox, postbox: component.context.account.postbox,
userLocation: .other, userLocation: .other,
photoReference: .story(peer: peerReference, id: component.item.id, media: image), photoReference: .story(peer: peerReference, id: component.item.id, media: image),
synchronousLoad: true, synchronousLoad: synchronousLoad,
highQuality: true highQuality: true
) )
if let representation = image.representations.last { if let representation = image.representations.last {
@ -377,7 +415,7 @@ final class StoryItemContentComponent: Component {
videoReference: .story(peer: peerReference, id: component.item.id, media: file), videoReference: .story(peer: peerReference, id: component.item.id, media: file),
onlyFullSize: false, onlyFullSize: false,
useLargeThumbnail: true, useLargeThumbnail: true,
synchronousLoad: true, synchronousLoad: synchronousLoad,
autoFetchFullSizeThumbnail: true, autoFetchFullSizeThumbnail: true,
overlayColor: nil, overlayColor: nil,
nilForEmptyResult: false, nilForEmptyResult: false,
@ -458,20 +496,6 @@ final class StoryItemContentComponent: Component {
self.imageNode.frame = CGRect(origin: CGPoint(), size: availableSize) 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 { switch component.item.media {
case .image, .file: case .image, .file:
if let unsupportedText = self.unsupportedText { if let unsupportedText = self.unsupportedText {
@ -565,7 +589,7 @@ final class StoryItemContentComponent: Component {
self.backgroundColor = UIColor(rgb: 0x181818) self.backgroundColor = UIColor(rgb: 0x181818)
} }
self.updateIsProgressPaused() self.updateIsProgressPaused(update: false)
return availableSize return availableSize
} }

View File

@ -29,6 +29,7 @@ import TextFormat
import LocalMediaResources import LocalMediaResources
import SaveToCameraRoll import SaveToCameraRoll
import BundleIconComponent import BundleIconComponent
import PeerListItemComponent
public final class StoryItemSetContainerComponent: Component { public final class StoryItemSetContainerComponent: Component {
public final class ExternalState { public final class ExternalState {
@ -42,6 +43,7 @@ public final class StoryItemSetContainerComponent: Component {
public enum NavigationDirection { public enum NavigationDirection {
case previous case previous
case next case next
case id(Int32)
} }
public struct PinchState: Equatable { public struct PinchState: Equatable {
@ -181,19 +183,33 @@ public final class StoryItemSetContainerComponent: Component {
struct ItemLayout { struct ItemLayout {
var size: CGSize var size: CGSize
var contentFrame: CGRect
var contentVisualScale: CGFloat
init(size: CGSize) { init(
size: CGSize,
contentFrame: CGRect,
contentVisualScale: CGFloat
) {
self.size = size self.size = size
self.contentFrame = contentFrame
self.contentVisualScale = contentVisualScale
} }
} }
final class VisibleItem { final class VisibleItem {
let externalState = StoryContentItem.ExternalState() let externalState = StoryContentItem.ExternalState()
let contentContainerView: UIView
let view = ComponentView<StoryContentItem.Environment>() let view = ComponentView<StoryContentItem.Environment>()
var currentProgress: Double = 0.0 var currentProgress: Double = 0.0
var requestedNext: Bool = false var requestedNext: Bool = false
init() { 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 sendMessageContext: StoryItemSetContainerSendMessage
let contentContainerView: UIView private let scroller: Scroller
let itemsContainerView: UIView
let controlsContainerView: UIView
let topContentGradientLayer: SimpleGradientLayer let topContentGradientLayer: SimpleGradientLayer
let bottomContentGradientLayer: SimpleGradientLayer let bottomContentGradientLayer: SimpleGradientLayer
let contentDimView: UIView let contentDimView: UIView
@ -249,6 +290,7 @@ public final class StoryItemSetContainerComponent: Component {
let inputPanelExternalState = MessageInputPanelComponent.ExternalState() let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
private let inputPanelBackground = ComponentView<Empty>() private let inputPanelBackground = ComponentView<Empty>()
var preparingToDisplayViewList: Bool = false
var displayViewList: Bool = false var displayViewList: Bool = false
var viewList: ViewList? var viewList: ViewList?
@ -257,9 +299,10 @@ public final class StoryItemSetContainerComponent: Component {
var itemLayout: ItemLayout? var itemLayout: ItemLayout?
var ignoreScrolling: Bool = false var ignoreScrolling: Bool = false
var visibleItems: [AnyHashable: VisibleItem] = [:] var visibleItems: [Int32: VisibleItem] = [:]
var trulyValidIds: [Int32] = []
var preloadContexts: [AnyHashable: Disposable] = [:] var scrollingOffsetX: CGFloat = 0.0
var scrollingCenterX: CGFloat = 0.0
var reactionItems: [ReactionItem]? var reactionItems: [ReactionItem]?
var reactionContextNode: ReactionContextNode? var reactionContextNode: ReactionContextNode?
@ -282,13 +325,25 @@ public final class StoryItemSetContainerComponent: Component {
let transitionCloneContainerView: UIView let transitionCloneContainerView: UIView
private var awaitingSwitchToId: (from: Int32, to: Int32)?
private var animateNextNavigationId: Int32?
private var initializedOffset: Bool = false
override init(frame: CGRect) { override init(frame: CGRect) {
self.sendMessageContext = StoryItemSetContainerSendMessage() self.sendMessageContext = StoryItemSetContainerSendMessage()
self.contentContainerView = UIView() self.itemsContainerView = UIView()
self.contentContainerView.clipsToBounds = true
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, *) { if #available(iOS 13.0, *) {
self.contentContainerView.layer.cornerCurve = .continuous self.controlsContainerView.layer.cornerCurve = .continuous
} }
self.topContentGradientLayer = SimpleGradientLayer() self.topContentGradientLayer = SimpleGradientLayer()
@ -304,18 +359,26 @@ public final class StoryItemSetContainerComponent: Component {
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.contentContainerView) self.clipsToBounds = true
self.contentContainerView.addSubview(self.contentDimView)
self.contentContainerView.layer.addSublayer(self.topContentGradientLayer) 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.layer.addSublayer(self.bottomContentGradientLayer)
self.closeButton.addSubview(self.closeButtonIconView) 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) self.closeButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
tapRecognizer.delegate = self tapRecognizer.delegate = self
self.contentContainerView.addGestureRecognizer(tapRecognizer) self.itemsContainerView.addGestureRecognizer(tapRecognizer)
self.audioRecorderDisposable = (self.sendMessageContext.audioRecorder.get() self.audioRecorderDisposable = (self.sendMessageContext.audioRecorder.get()
|> deliverOnMainQueue).start(next: { [weak self] audioRecorder in |> 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 return true
} }
@ -448,7 +511,7 @@ public final class StoryItemSetContainerComponent: Component {
guard let component = self.component else { guard let component = self.component else {
return return
} }
guard let visibleItem = self.visibleItems[component.slice.item.id] else { guard let visibleItem = self.visibleItems[component.slice.item.storyItem.id] else {
return return
} }
if let itemView = visibleItem.view.view as? StoryContentItem.View { if let itemView = visibleItem.view.view as? StoryContentItem.View {
@ -460,7 +523,7 @@ public final class StoryItemSetContainerComponent: Component {
guard let component = self.component else { guard let component = self.component else {
return return
} }
guard let visibleItem = self.visibleItems[component.slice.item.id] else { guard let visibleItem = self.visibleItems[component.slice.item.storyItem.id] else {
return return
} }
if let itemView = visibleItem.view.view as? StoryContentItem.View { if let itemView = visibleItem.view.view as? StoryContentItem.View {
@ -481,8 +544,21 @@ public final class StoryItemSetContainerComponent: Component {
self.sendMessageContext.currentInputMode = .text self.sendMessageContext.currentInputMode = .text
self.endEditing(true) self.endEditing(true)
} else if self.displayViewList { } else if self.displayViewList {
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.displayViewList = false
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) 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 { } else if let captionItem = self.captionItem, captionItem.externalState.isExpanded {
if let captionItemView = captionItem.view.view as? StoryContentCaptionComponent.View { if let captionItemView = captionItem.view.view as? StoryContentCaptionComponent.View {
captionItemView.collapse(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) 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) { if let inputView = self.inputPanel.view, let inputViewHitTest = inputView.hitTest(self.convert(point, to: inputView), with: event) {
return inputViewHitTest 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<CGPoint>) {
}
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 { private func isProgressPaused() -> Bool {
@ -563,19 +750,75 @@ public final class StoryItemSetContainerComponent: Component {
return return
} }
var validIds: [AnyHashable] = [] var validIds: [Int32] = []
let focusedItem = component.slice.item var trulyValidIds: [Int32] = []
validIds.append(focusedItem.id) let centralItemFrame = itemLayout.contentFrame.center.offsetBy(dx: 0.0, dy: 0.0)
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]
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 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 var itemTransition = transition
let visibleItem: VisibleItem let visibleItem: VisibleItem
if let current = self.visibleItems[focusedItem.id] { if let current = self.visibleItems[item.storyItem.id] {
visibleItem = current visibleItem = current
} else { } else {
itemTransition = .immediate itemTransition = .immediate
visibleItem = VisibleItem() visibleItem = VisibleItem()
self.visibleItems[focusedItem.id] = visibleItem self.visibleItems[item.storyItem.id] = visibleItem
} }
let itemEnvironment = StoryContentItem.Environment( let itemEnvironment = StoryContentItem.Environment(
@ -608,31 +851,77 @@ public final class StoryItemSetContainerComponent: Component {
} }
) )
let _ = visibleItem.view.update( let _ = visibleItem.view.update(
transition: itemTransition, transition: itemTransition.withUserData(StoryItemContentComponent.Hint(
component: focusedItem.component, synchronousLoad: index == centralIndex
)),
component: AnyComponent(StoryItemContentComponent(
context: component.context,
peer: component.slice.peer,
item: item.storyItem
)),
environment: { environment: {
itemEnvironment itemEnvironment
}, },
containerSize: itemLayout.size containerSize: itemLayout.size
) )
if let view = visibleItem.view.view { if let view = visibleItem.view.view {
if view.superview == nil { if visibleItem.contentContainerView.superview == nil {
self.contentContainerView.insertSubview(view, at: 0) self.itemsContainerView.addSubview(visibleItem.contentContainerView)
visibleItem.contentContainerView.addSubview(view)
}
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
} }
itemTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: itemLayout.size))
if let view = view as? StoryContentItem.View { if let view = view as? StoryContentItem.View {
view.setIsProgressPaused(self.isProgressPaused()) view.setIsProgressPaused(itemProgressPaused)
}
}
} }
} }
var removeIds: [AnyHashable] = [] self.trulyValidIds = trulyValidIds
var removeIds: [Int32] = []
for (id, visibleItem) in self.visibleItems { for (id, visibleItem) in self.visibleItems {
if !validIds.contains(id) { if !validIds.contains(id) {
removeIds.append(id) removeIds.append(id)
if let view = visibleItem.view.view { visibleItem.contentContainerView.removeFromSuperview()
view.removeFromSuperview()
}
} }
} }
for id in removeIds { for id in removeIds {
@ -641,29 +930,43 @@ public final class StoryItemSetContainerComponent: Component {
} }
func updateIsProgressPaused() { 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 = visibleItem.view.view {
if let view = view as? StoryContentItem.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 { guard let component = self.component else {
return return false
} }
if component.slice.peer.id == component.context.account.peerId { if component.slice.peer.id == component.context.account.peerId {
if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty { if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty {
self.displayViewList = true 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))) self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
return true
} }
} else { } else {
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View { if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
inputPanelView.activateInput() inputPanelView.activateInput()
return false
} }
} }
return false
} }
func animateIn(transitionIn: StoryContainerScreen.TransitionIn) { func animateIn(transitionIn: StoryContainerScreen.TransitionIn) {
@ -681,7 +984,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
if let viewListView = self.viewList?.view.view { if let viewListView = self.viewList?.view.view {
viewListView.layer.animatePosition( 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(), to: CGPoint(),
duration: 0.3, duration: 0.3,
timingFunction: kCAMediaTimingFunctionSpring, timingFunction: kCAMediaTimingFunctionSpring,
@ -700,9 +1003,9 @@ public final class StoryItemSetContainerComponent: Component {
captionItemView.layer.animateAlpha(from: 0.0, to: captionItemView.alpha, duration: 0.28) 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 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 { if let centerInfoView = self.centerInfoItem?.view.view {
centerInfoView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) 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) contentContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: 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) contentContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: contentContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.contentContainerView.layer.animate( contentContainerView.layer.animate(
from: transitionIn.sourceCornerRadius as NSNumber, from: transitionIn.sourceCornerRadius as NSNumber,
to: self.contentContainerView.layer.cornerRadius as NSNumber, to: contentContainerView.layer.cornerRadius as NSNumber,
keyPath: "cornerRadius", keyPath: "cornerRadius",
timingFunction: kCAMediaTimingFunctionSpring, timingFunction: kCAMediaTimingFunctionSpring,
duration: 0.3 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 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)) 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 { if let viewListView = self.viewList?.view.view {
viewListView.layer.animatePosition( viewListView.layer.animatePosition(
from: CGPoint(), 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, duration: 0.3,
timingFunction: kCAMediaTimingFunctionSpring, timingFunction: kCAMediaTimingFunctionSpring,
removeOnCompletion: false, 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) 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 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 { if let centerInfoView = self.centerInfoItem?.view.view {
centerInfoView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) 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) let transitionSourceContainerView = UIView(frame: self.bounds)
transitionSourceContainerView.isUserInteractionEnabled = false transitionSourceContainerView.isUserInteractionEnabled = false
self.insertSubview(transitionSourceContainerView, aboveSubview: self.contentContainerView) self.insertSubview(transitionSourceContainerView, aboveSubview: self.itemsContainerView)
transitionSourceContainerView.addSubview(transitionViewImpl) 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) contentContainerView.layer.animatePosition(from: 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) 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)
self.contentContainerView.layer.animate( contentContainerView.layer.animate(
from: self.contentContainerView.layer.cornerRadius as NSNumber, 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, to: transitionOut.destinationCornerRadius as NSNumber,
keyPath: "cornerRadius", keyPath: "cornerRadius",
timingFunction: kCAMediaTimingFunctionSpring, timingFunction: kCAMediaTimingFunctionSpring,
@ -912,7 +1236,7 @@ public final class StoryItemSetContainerComponent: Component {
let transitionSourceContainerView = UIView(frame: self.bounds) let transitionSourceContainerView = UIView(frame: self.bounds)
transitionSourceContainerView.isUserInteractionEnabled = false transitionSourceContainerView.isUserInteractionEnabled = false
self.insertSubview(transitionSourceContainerView, belowSubview: self.contentContainerView) self.insertSubview(transitionSourceContainerView, belowSubview: self.itemsContainerView)
transitionSourceContainerView.addSubview(transitionViewImpl) transitionSourceContainerView.addSubview(transitionViewImpl)
@ -954,7 +1278,8 @@ public final class StoryItemSetContainerComponent: Component {
transitionViewImpl.alpha = 1.0 transitionViewImpl.alpha = 1.0
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) 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 { for transitionViewImpl in transitionViewsImpl {
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) 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 let innerScale = innerSourceLocalFrame.width / visibleItemView.bounds.width
var adjustedInnerSourceLocalFrame = innerSourceLocalFrame var adjustedInnerSourceLocalFrame = innerSourceLocalFrame
@ -1031,6 +1356,12 @@ public final class StoryItemSetContainerComponent: Component {
if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id { 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)) 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 { 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.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3)
} }
//self.updatePreloads()
let wasPanning = self.component?.isPanning ?? false let wasPanning = self.component?.isPanning ?? false
self.component = component self.component = component
self.state = state self.state = state
@ -1416,7 +1745,9 @@ public final class StoryItemSetContainerComponent: Component {
viewList.view.parentState = state viewList.view.parentState = state
let viewListSize = viewList.view.update( let viewListSize = viewList.view.update(
transition: viewListTransition, transition: viewListTransition.withUserData(PeerListItemComponent.TransitionHint(
synchronousLoad: false
)),
component: AnyComponent(StoryItemSetViewListComponent( component: AnyComponent(StoryItemSetViewListComponent(
externalState: viewList.externalState, externalState: viewList.externalState,
context: component.context, context: component.context,
@ -1439,6 +1770,11 @@ public final class StoryItemSetContainerComponent: Component {
if !self.displayViewList { if !self.displayViewList {
self.displayViewList = true 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))) 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) 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) let itemLayout = ItemLayout(
transition.setBounds(view: self.contentContainerView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size)) 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) var transform = CATransform3DMakeScale(contentVisualScale, contentVisualScale, 1.0)
if let pinchState = component.pinchState { if let pinchState = component.pinchState {
@ -1728,10 +2073,9 @@ public final class StoryItemSetContainerComponent: Component {
) )
transform = CATransform3DScale(transform, pinchState.scale, pinchState.scale, 0.0) 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.controlsContainerView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale))
transition.setCornerRadius(layer: self.contentContainerView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale))
if self.closeButtonIconView.image == nil { if self.closeButtonIconView.image == nil {
self.closeButtonIconView.image = UIImage(bundleImageName: "Media Gallery/Close")?.withRenderingMode(.alwaysTemplate) 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)) 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.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.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: StoryContentItem? = component.slice.item
let _ = focusedItem let _ = focusedItem
/*if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) { /*if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) {
@ -1751,15 +2096,14 @@ public final class StoryItemSetContainerComponent: Component {
}*/ }*/
var currentRightInfoItem: InfoItem? var currentRightInfoItem: InfoItem?
if let focusedItem { if focusedItem != nil {
if let rightInfoComponent = focusedItem.rightInfoComponent { let rightInfoComponent = AnyComponent(StoryAvatarInfoComponent(context: component.context, peer: component.slice.peer))
if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == focusedItem.rightInfoComponent { if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == rightInfoComponent {
currentRightInfoItem = rightInfoItem currentRightInfoItem = rightInfoItem
} else { } else {
currentRightInfoItem = InfoItem(component: rightInfoComponent) currentRightInfoItem = InfoItem(component: rightInfoComponent)
} }
} }
}
if let rightInfoItem = self.rightInfoItem, currentRightInfoItem?.component != rightInfoItem.component { if let rightInfoItem = self.rightInfoItem, currentRightInfoItem?.component != rightInfoItem.component {
self.rightInfoItem = nil self.rightInfoItem = nil
@ -1772,15 +2116,14 @@ public final class StoryItemSetContainerComponent: Component {
} }
var currentCenterInfoItem: InfoItem? var currentCenterInfoItem: InfoItem?
if let focusedItem { if focusedItem != nil {
if let centerInfoComponent = focusedItem.centerInfoComponent { let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent(context: component.context, peer: component.slice.peer, timestamp: component.slice.item.storyItem.timestamp))
if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == focusedItem.centerInfoComponent { if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == centerInfoComponent {
currentCenterInfoItem = centerInfoItem currentCenterInfoItem = centerInfoItem
} else { } else {
currentCenterInfoItem = InfoItem(component: centerInfoComponent) currentCenterInfoItem = InfoItem(component: centerInfoComponent)
} }
} }
}
if let centerInfoItem = self.centerInfoItem, currentCenterInfoItem?.component != centerInfoItem.component { if let centerInfoItem = self.centerInfoItem, currentCenterInfoItem?.component != centerInfoItem.component {
self.centerInfoItem = nil self.centerInfoItem = nil
@ -1806,7 +2149,7 @@ public final class StoryItemSetContainerComponent: Component {
if let view = currentCenterInfoItem.view.view { if let view = currentCenterInfoItem.view.view {
var animateIn = false var animateIn = false
if view.superview == nil { if view.superview == nil {
self.contentContainerView.insertSubview(view, belowSubview: self.closeButton) self.controlsContainerView.insertSubview(view, belowSubview: self.closeButton)
animateIn = true animateIn = true
} }
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: centerInfoItemSize)) 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) //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 { if let view = currentRightInfoItem.view.view {
var animateIn = false var animateIn = false
if view.superview == nil { if view.superview == nil {
self.contentContainerView.addSubview(view) self.controlsContainerView.addSubview(view)
animateIn = true animateIn = true
} }
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: contentFrame.width - 6.0 - rightInfoItemSize.width, y: 14.0), size: rightInfoItemSize)) 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) 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) 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 let closeFriendIconView = closeFriendIcon.view {
if closeFriendIconView.superview == nil { if closeFriendIconView.superview == nil {
self.contentContainerView.addSubview(closeFriendIconView) self.controlsContainerView.addSubview(closeFriendIconView)
closeFriendIconTransition.setFrame(view: closeFriendIconView, frame: closeFriendIconFrame) 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.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) 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) 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 { if case .regular = component.metrics.widthClass {
inputPanelAlpha *= component.visibilityFraction inputPanelAlpha *= component.visibilityFraction
} }
@ -1929,7 +2268,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
var inputPanelOffset: CGFloat = 0.0 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) let bandingOffset = scrollingRubberBandingOffset(offset: component.verticalPanFraction * availableSize.height, bandingStart: 0.0, range: 10.0)
inputPanelOffset = -max(0.0, min(10.0, bandingOffset)) 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) let captionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.height - captionSize.height), size: captionSize)
if let captionItemView = captionItem.view.view { if let captionItemView = captionItem.view.view {
if captionItemView.superview == nil { 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.setFrame(view: captionItemView, frame: captionFrame)
captionItemTransition.setAlpha(view: captionItemView, alpha: (component.hideUI || self.displayViewList || self.isEditingStory || self.inputPanelExternalState.isEditing) ? 0.0 : 1.0) 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) 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 { if let focusedItem, let visibleItem = self.visibleItems[focusedItem.storyItem.id], let index = focusedItem.position {
let navigationStripSideInset: CGFloat = 8.0 let navigationStripSideInset: CGFloat = 8.0
@ -2294,15 +2638,15 @@ public final class StoryItemSetContainerComponent: Component {
if let navigationStripView = self.navigationStrip.view { if let navigationStripView = self.navigationStrip.view {
if navigationStripView.superview == nil { if navigationStripView.superview == nil {
navigationStripView.isUserInteractionEnabled = false 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.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 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 component.externalState.derivedBottomInset = availableSize.height - contentFrame.maxY
} else { } else {
component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrame.minY, contentFrame.maxY) component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrame.minY, contentFrame.maxY)

View File

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

View File

@ -95,7 +95,6 @@ import ICloudResources
import LegacyCamera import LegacyCamera
import LegacyInstantVideoController import LegacyInstantVideoController
import StoryContainerScreen import StoryContainerScreen
import StoryContentComponent
import MoreHeaderButton import MoreHeaderButton
import VolumeButtons import VolumeButtons
import ChatAvatarNavigationNode import ChatAvatarNavigationNode

View File

@ -23,7 +23,6 @@ import UndoUI
import WebsiteType import WebsiteType
import GalleryData import GalleryData
import StoryContainerScreen import StoryContainerScreen
import StoryContentComponent
func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
var story: TelegramMediaStory? var story: TelegramMediaStory?

View File

@ -31,7 +31,6 @@ import PremiumUI
import AuthorizationUI import AuthorizationUI
import ChatFolderLinkPreviewScreen import ChatFolderLinkPreviewScreen
import StoryContainerScreen import StoryContainerScreen
import StoryContentComponent
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer { private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
if case .default = navigation { if case .default = navigation {

View File

@ -89,7 +89,6 @@ import SendInviteLinkScreen
import PeerInfoVisualMediaPaneNode import PeerInfoVisualMediaPaneNode
import PeerInfoStoryGridScreen import PeerInfoStoryGridScreen
import StoryContainerScreen import StoryContainerScreen
import StoryContentComponent
import ChatAvatarNavigationNode import ChatAvatarNavigationNode
import PeerReportScreen import PeerReportScreen