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/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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -598,6 +598,21 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
updateNotificationsView({})
})
}, 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 _ = (
context.engine.peers.removeCustomNotificationSettings(peerIds: [peerId])
|> map { _ -> EnginePeer? in }
@ -611,6 +626,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
}
updateNotificationsView({})
})
}
}, modifiedPeer: {
}))
@ -699,6 +715,23 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
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 })
|> deliverOnMainQueue).start(completed: {
updateNotificationsDisposable.set(nil)
@ -714,6 +747,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
updateNotificationsView({})
})
})
}
})
]), ActionSheetItemGroup(items: [
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)
}
}, 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])
|> deliverOnMainQueue).start(completed: {
updateNotificationsDisposable.set(nil)
@ -737,6 +784,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
updateNotificationsView({})
})
})
}
}, updatedExceptionMode: { mode in
_ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels, stories) in
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)])
#if DEBUG && true
#if DEBUG && false
if "".isEmpty {
return .complete()
}

View File

@ -303,6 +303,15 @@ public extension TelegramEngine {
|> 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 {
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/SliderContextItem:SliderContextItem",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/Stories/StoryContentComponent",
"//submodules/TelegramUI/Components/CameraScreen",
"//submodules/TelegramUI/Components/MediaEditorScreen",
"//submodules/TelegramUI/Components/ChatScheduleTimeController",

View File

@ -249,15 +249,10 @@ 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)
}
default:
values[peerId] = value.updateSettings({$0.withUpdatedStoriesMuted(storiesMuted)}).withUpdatedDate(Date().timeIntervalSince1970)
}
} else {
switch storyNotifications {
case .default:

View File

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

View File

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

View File

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

View File

@ -75,14 +75,14 @@ private final class StoryContainerScreenComponent: Component {
let content: StoryContentContext
let focusedItemPromise: Promise<StoryId?>
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<StoryId?>,
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,17 +397,26 @@ 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))
}

View File

@ -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<StoryContentItem.Environment>
public let centerInfoComponent: AnyComponent<Empty>?
public let rightInfoComponent: AnyComponent<Empty>?
public let peerId: EnginePeer.Id?
public let storyItem: EngineStoryItem
public let isMy: Bool
public init(
id: AnyHashable,
position: Int?,
component: AnyComponent<StoryContentItem.Environment>,
centerInfoComponent: AnyComponent<Empty>?,
rightInfoComponent: AnyComponent<Empty>?,
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<StoryContentItemSlice, NoError>
public init(
id: AnyHashable,
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
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 {

View File

@ -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<Empty>?
private var unsupportedButton: ComponentView<Empty>?
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
}

View File

@ -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<StoryContentItem.Environment>()
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<Empty>()
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 {
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<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 {
@ -563,19 +750,75 @@ public final class StoryItemSetContainerComponent: Component {
return
}
var validIds: [AnyHashable] = []
let focusedItem = component.slice.item
var validIds: [Int32] = []
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
let visibleItem: VisibleItem
if let current = self.visibleItems[focusedItem.id] {
if let current = self.visibleItems[item.storyItem.id] {
visibleItem = current
} else {
itemTransition = .immediate
visibleItem = VisibleItem()
self.visibleItems[focusedItem.id] = visibleItem
self.visibleItems[item.storyItem.id] = visibleItem
}
let itemEnvironment = StoryContentItem.Environment(
@ -608,31 +851,77 @@ public final class StoryItemSetContainerComponent: Component {
}
)
let _ = visibleItem.view.update(
transition: itemTransition,
component: focusedItem.component,
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 view.superview == nil {
self.contentContainerView.insertSubview(view, at: 0)
if visibleItem.contentContainerView.superview == nil {
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 {
view.setIsProgressPaused(self.isProgressPaused())
view.setIsProgressPaused(itemProgressPaused)
}
}
}
}
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,15 +2096,14 @@ 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 {
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)
}
}
}
if let rightInfoItem = self.rightInfoItem, currentRightInfoItem?.component != rightInfoItem.component {
self.rightInfoItem = nil
@ -1772,15 +2116,14 @@ 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 {
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)
}
}
}
if let centerInfoItem = self.centerInfoItem, currentCenterInfoItem?.component != centerInfoItem.component {
self.centerInfoItem = nil
@ -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)

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 LegacyInstantVideoController
import StoryContainerScreen
import StoryContentComponent
import MoreHeaderButton
import VolumeButtons
import ChatAvatarNavigationNode

View File

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

View File

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

View File

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