mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Story optimizations
This commit is contained in:
parent
a35f38c0f6
commit
4353823603
@ -1027,6 +1027,8 @@ public protocol AccountContext: AnyObject {
|
|||||||
|
|
||||||
var userLimits: EngineConfiguration.UserLimits { get }
|
var userLimits: EngineConfiguration.UserLimits { get }
|
||||||
|
|
||||||
|
var imageCache: AnyObject? { get }
|
||||||
|
|
||||||
func storeSecureIdPassword(password: String)
|
func storeSecureIdPassword(password: String)
|
||||||
func getStoredSecureIdPassword() -> String?
|
func getStoredSecureIdPassword() -> String?
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ swift_library(
|
|||||||
"//submodules/FastBlur:FastBlur",
|
"//submodules/FastBlur:FastBlur",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||||
|
"//submodules/DirectMediaImageCache",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -13,6 +13,7 @@ import Emoji
|
|||||||
import Accelerate
|
import Accelerate
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import AvatarStoryIndicatorComponent
|
import AvatarStoryIndicatorComponent
|
||||||
|
import DirectMediaImageCache
|
||||||
|
|
||||||
private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed()
|
private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed()
|
||||||
private let phoneIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/PhoneIcon"), color: .white)
|
private let phoneIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/PhoneIcon"), color: .white)
|
||||||
@ -228,6 +229,19 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
]
|
]
|
||||||
|
|
||||||
public final class ContentNode: ASDisplayNode {
|
public final class ContentNode: ASDisplayNode {
|
||||||
|
private struct Params: Equatable {
|
||||||
|
let peerId: EnginePeer.Id?
|
||||||
|
let resourceId: String?
|
||||||
|
|
||||||
|
init(
|
||||||
|
peerId: EnginePeer.Id?,
|
||||||
|
resourceId: String?
|
||||||
|
) {
|
||||||
|
self.peerId = peerId
|
||||||
|
self.resourceId = resourceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var font: UIFont {
|
public var font: UIFont {
|
||||||
didSet {
|
didSet {
|
||||||
if oldValue.pointSize != font.pointSize {
|
if oldValue.pointSize != font.pointSize {
|
||||||
@ -255,6 +269,9 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
public var unroundedImage: UIImage?
|
public var unroundedImage: UIImage?
|
||||||
private var currentImage: UIImage?
|
private var currentImage: UIImage?
|
||||||
|
|
||||||
|
private var params: Params?
|
||||||
|
private var loadDisposable: Disposable?
|
||||||
|
|
||||||
public var badgeView: AvatarBadgeView? {
|
public var badgeView: AvatarBadgeView? {
|
||||||
didSet {
|
didSet {
|
||||||
if self.badgeView !== oldValue {
|
if self.badgeView !== oldValue {
|
||||||
@ -319,6 +336,10 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.loadDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
override public func didLoad() {
|
override public func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
@ -496,6 +517,58 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setPeerV2(
|
||||||
|
context genericContext: AccountContext,
|
||||||
|
account: Account? = nil,
|
||||||
|
theme: PresentationTheme,
|
||||||
|
peer: EnginePeer?,
|
||||||
|
authorOfMessage: MessageReference? = nil,
|
||||||
|
overrideImage: AvatarNodeImageOverride? = nil,
|
||||||
|
emptyColor: UIColor? = nil,
|
||||||
|
clipStyle: AvatarNodeClipStyle = .round,
|
||||||
|
synchronousLoad: Bool = false,
|
||||||
|
displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0),
|
||||||
|
storeUnrounded: Bool = false
|
||||||
|
) {
|
||||||
|
let smallProfileImage = peer?.smallProfileImage
|
||||||
|
let params = Params(
|
||||||
|
peerId: peer?.id,
|
||||||
|
resourceId: smallProfileImage?.resource.id.stringRepresentation
|
||||||
|
)
|
||||||
|
if self.params == params {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
switch clipStyle {
|
||||||
|
case .none:
|
||||||
|
self.imageNode.clipsToBounds = false
|
||||||
|
self.imageNode.cornerRadius = 0.0
|
||||||
|
case .round:
|
||||||
|
self.imageNode.clipsToBounds = true
|
||||||
|
self.imageNode.cornerRadius = displayDimensions.height * 0.5
|
||||||
|
case .roundedRect:
|
||||||
|
self.imageNode.clipsToBounds = true
|
||||||
|
self.imageNode.cornerRadius = displayDimensions.height * 0.25
|
||||||
|
}
|
||||||
|
|
||||||
|
if let imageCache = genericContext.imageCache as? DirectMediaImageCache, let peer, let smallProfileImage = peer.smallProfileImage, let peerReference = PeerReference(peer._asPeer()) {
|
||||||
|
if let result = imageCache.getAvatarImage(peer: peerReference, resource: MediaResourceReference.avatar(peer: peerReference, resource: smallProfileImage.resource), immediateThumbnail: peer.profileImageRepresentations.first?.immediateThumbnailData, size: Int(displayDimensions.width * UIScreenScale), synchronous: synchronousLoad) {
|
||||||
|
if let image = result.image {
|
||||||
|
self.imageNode.contents = image.cgImage
|
||||||
|
}
|
||||||
|
if let loadSignal = result.loadSignal {
|
||||||
|
self.loadDisposable = (loadSignal |> deliverOnMainQueue).start(next: { [weak self] image in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.imageNode.contents = image?.cgImage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func setPeer(
|
public func setPeer(
|
||||||
context genericContext: AccountContext,
|
context genericContext: AccountContext,
|
||||||
account: Account? = nil,
|
account: Account? = nil,
|
||||||
@ -657,6 +730,10 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
context.fill(bounds)
|
context.fill(bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !(parameters is AvatarNodeParameters) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let colors: [UIColor]
|
let colors: [UIColor]
|
||||||
if let parameters = parameters as? AvatarNodeParameters {
|
if let parameters = parameters as? AvatarNodeParameters {
|
||||||
colors = parameters.colors
|
colors = parameters.colors
|
||||||
@ -925,6 +1002,32 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setPeerV2(
|
||||||
|
context genericContext: AccountContext,
|
||||||
|
theme: PresentationTheme,
|
||||||
|
peer: EnginePeer?,
|
||||||
|
authorOfMessage: MessageReference? = nil,
|
||||||
|
overrideImage: AvatarNodeImageOverride? = nil,
|
||||||
|
emptyColor: UIColor? = nil,
|
||||||
|
clipStyle: AvatarNodeClipStyle = .round,
|
||||||
|
synchronousLoad: Bool = false,
|
||||||
|
displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0),
|
||||||
|
storeUnrounded: Bool = false
|
||||||
|
) {
|
||||||
|
self.contentNode.setPeerV2(
|
||||||
|
context: genericContext,
|
||||||
|
theme: theme,
|
||||||
|
peer: peer,
|
||||||
|
authorOfMessage: authorOfMessage,
|
||||||
|
overrideImage: overrideImage,
|
||||||
|
emptyColor: emptyColor,
|
||||||
|
clipStyle: clipStyle,
|
||||||
|
synchronousLoad: synchronousLoad,
|
||||||
|
displayDimensions: displayDimensions,
|
||||||
|
storeUnrounded: storeUnrounded
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
public func setPeer(
|
public func setPeer(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
account: Account? = nil,
|
account: Account? = nil,
|
||||||
|
@ -491,6 +491,33 @@ public final class DirectMediaImageCache {
|
|||||||
|
|
||||||
return GetMediaResult(image: resultImage, blurredImage: blurredImage, loadSignal: self.getLoadSignal(width: width, aspectRatio: aspectRatio, userLocation: userLocation, userContentType: .image, resource: resource.resource, resourceSizeLimit: resource.size))
|
return GetMediaResult(image: resultImage, blurredImage: blurredImage, loadSignal: self.getLoadSignal(width: width, aspectRatio: aspectRatio, userLocation: userLocation, userContentType: .image, resource: resource.resource, resourceSizeLimit: resource.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func getAvatarImageSynchronous(peer: PeerReference, resource: MediaResourceReference, immediateThumbnail: Data?, size: Int, includeBlurred: Bool) -> GetMediaResult? {
|
||||||
|
let immediateThumbnailData: Data? = immediateThumbnail
|
||||||
|
|
||||||
|
var blurredImage: UIImage?
|
||||||
|
if includeBlurred, let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = loadImage(data: data), let blurredImageValue = generateBlurredThumbnail(image: image, adjustSaturation: true) {
|
||||||
|
blurredImage = blurredImageValue
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultImage: UIImage?
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .square(width: size, aspectRatio: 1.0)))), let image = loadImage(data: data) {
|
||||||
|
return GetMediaResult(image: image, blurredImage: blurredImage, loadSignal: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultImage == nil {
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .blurredThumbnail))), let image = loadImage(data: data) {
|
||||||
|
resultImage = image
|
||||||
|
} else if let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = loadImage(data: data) {
|
||||||
|
if let blurredImageValue = generateBlurredThumbnail(image: image) {
|
||||||
|
resultImage = blurredImageValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetMediaResult(image: resultImage, blurredImage: blurredImage, loadSignal: self.getLoadSignal(width: size, aspectRatio: 1.0, userLocation: .other, userContentType: .avatar, resource: resource, resourceSizeLimit: 1 * 1024 * 1024))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public func getImage(peer: PeerReference, story: EngineStoryItem, media: Media, width: Int, aspectRatio: CGFloat, possibleWidths: [Int], includeBlurred: Bool = false, synchronous: Bool) -> GetMediaResult? {
|
public func getImage(peer: PeerReference, story: EngineStoryItem, media: Media, width: Int, aspectRatio: CGFloat, possibleWidths: [Int], includeBlurred: Bool = false, synchronous: Bool) -> GetMediaResult? {
|
||||||
if synchronous {
|
if synchronous {
|
||||||
@ -530,4 +557,37 @@ public final class DirectMediaImageCache {
|
|||||||
|> runOn(.concurrentDefaultQueue()))
|
|> runOn(.concurrentDefaultQueue()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getAvatarImage(peer: PeerReference, resource: MediaResourceReference, immediateThumbnail: Data?, size: Int, includeBlurred: Bool = false, synchronous: Bool) -> GetMediaResult? {
|
||||||
|
if synchronous {
|
||||||
|
return self.getAvatarImageSynchronous(peer: peer, resource: resource, immediateThumbnail: immediateThumbnail, size: size, includeBlurred: includeBlurred)
|
||||||
|
} else {
|
||||||
|
var blurredImage: UIImage?
|
||||||
|
if includeBlurred, let data = immediateThumbnail.flatMap(decodeTinyThumbnail), let image = loadImage(data: data), let blurredImageValue = generateBlurredThumbnail(image: image, adjustSaturation: true) {
|
||||||
|
blurredImage = blurredImageValue
|
||||||
|
}
|
||||||
|
return GetMediaResult(image: nil, blurredImage: blurredImage, loadSignal: Signal { subscriber in
|
||||||
|
let result = self.getAvatarImageSynchronous(peer: peer, resource: resource, immediateThumbnail: immediateThumbnail, size: size, includeBlurred: includeBlurred)
|
||||||
|
guard let result = result else {
|
||||||
|
subscriber.putNext(nil)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|
||||||
|
if let image = result.image {
|
||||||
|
subscriber.putNext(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let signal = result.loadSignal {
|
||||||
|
return signal.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||||
|
} else {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(.concurrentDefaultQueue()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,6 +451,14 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func hasFirstResponder() -> Bool {
|
||||||
|
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
||||||
|
return textFieldView.hasFirstResponder()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func getSendMessageInput() -> SendMessageInput {
|
public func getSendMessageInput() -> SendMessageInput {
|
||||||
guard let textFieldView = self.textField.view as? TextFieldComponent.View else {
|
guard let textFieldView = self.textField.view as? TextFieldComponent.View else {
|
||||||
return .text(NSAttributedString())
|
return .text(NSAttributedString())
|
||||||
|
@ -574,8 +574,8 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
|||||||
return self.chatPresentationData.theme.theme.list.itemPlainSeparatorColor
|
return self.chatPresentationData.theme.theme.list.itemPlainSeparatorColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func createLayer() -> SparseItemGridLayer? {
|
func createLayer(item: SparseItemGrid.Item) -> SparseItemGridLayer? {
|
||||||
if self.captureProtected {
|
if let item = item as? VisualMediaItem, item.story.isForwardingDisabled {
|
||||||
return CaptureProtectedItemLayer()
|
return CaptureProtectedItemLayer()
|
||||||
} else {
|
} else {
|
||||||
return GenericItemLayer()
|
return GenericItemLayer()
|
||||||
|
@ -941,7 +941,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
|||||||
return self.chatPresentationData.theme.theme.list.itemPlainSeparatorColor
|
return self.chatPresentationData.theme.theme.list.itemPlainSeparatorColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func createLayer() -> SparseItemGridLayer? {
|
func createLayer(item: SparseItemGrid.Item) -> SparseItemGridLayer? {
|
||||||
if self.useListItems {
|
if self.useListItems {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -501,7 +501,22 @@ public final class PeerListItemComponent: Component {
|
|||||||
}
|
}
|
||||||
let _ = clipStyle
|
let _ = clipStyle
|
||||||
let _ = synchronousLoad
|
let _ = synchronousLoad
|
||||||
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
|
||||||
|
if peer.smallProfileImage != nil {
|
||||||
|
self.avatarNode.setPeerV2(
|
||||||
|
context: component.context,
|
||||||
|
theme: component.theme,
|
||||||
|
peer: peer,
|
||||||
|
authorOfMessage: nil,
|
||||||
|
overrideImage: nil,
|
||||||
|
emptyColor: nil,
|
||||||
|
clipStyle: .round,
|
||||||
|
synchronousLoad: synchronousLoad,
|
||||||
|
displayDimensions: CGSize(width: avatarSize, height: avatarSize)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
||||||
|
}
|
||||||
self.avatarNode.setStoryStats(storyStats: component.storyStats.flatMap { storyStats -> AvatarNode.StoryStats in
|
self.avatarNode.setStoryStats(storyStats: component.storyStats.flatMap { storyStats -> AvatarNode.StoryStats in
|
||||||
return AvatarNode.StoryStats(
|
return AvatarNode.StoryStats(
|
||||||
totalCount: storyStats.totalCount == 0 ? 0 : 1,
|
totalCount: storyStats.totalCount == 0 ? 0 : 1,
|
||||||
|
@ -1210,7 +1210,9 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
if component.content.stateValue?.slice == nil {
|
if component.content.stateValue?.slice == nil {
|
||||||
self.environment?.controller()?.dismiss()
|
self.environment?.controller()?.dismiss()
|
||||||
} else {
|
} else {
|
||||||
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
print("update time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
@ -2324,6 +2324,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
let isFirstTime = self.component == nil
|
let isFirstTime = self.component == nil
|
||||||
|
|
||||||
|
let startTime1 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
if self.component == nil {
|
if self.component == nil {
|
||||||
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
|
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
|
||||||
@ -2470,6 +2472,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
component.externalState.dismissFraction = dismissFraction
|
component.externalState.dismissFraction = dismissFraction
|
||||||
|
|
||||||
|
let startTime2 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
transition.setPosition(view: self.componentContainerView, position: CGPoint(x: availableSize.width * 0.5, y: availableSize.height * 0.5 + dismissPanOffset))
|
transition.setPosition(view: self.componentContainerView, position: CGPoint(x: availableSize.width * 0.5, y: availableSize.height * 0.5 + dismissPanOffset))
|
||||||
transition.setBounds(view: self.componentContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setBounds(view: self.componentContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
transition.setScale(view: self.componentContainerView, scale: dismissPanScale)
|
transition.setScale(view: self.componentContainerView, scale: dismissPanScale)
|
||||||
@ -2481,6 +2485,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
transition.setBounds(view: self.overlayContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setBounds(view: self.overlayContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
transition.setScale(view: self.overlayContainerView, scale: dismissPanScale)
|
transition.setScale(view: self.overlayContainerView, scale: dismissPanScale)
|
||||||
|
|
||||||
|
let startTime21 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
var bottomContentInset: CGFloat
|
var bottomContentInset: CGFloat
|
||||||
if !component.safeInsets.bottom.isZero {
|
if !component.safeInsets.bottom.isZero {
|
||||||
bottomContentInset = component.safeInsets.bottom + 1.0
|
bottomContentInset = component.safeInsets.bottom + 1.0
|
||||||
@ -2531,220 +2537,242 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
inputPlaceholder = .plain(component.strings.Story_InputPlaceholderReplyPrivately)
|
inputPlaceholder = .plain(component.strings.Story_InputPlaceholderReplyPrivately)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let startTime22 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
|
var currentHasFirstResponder = false
|
||||||
|
if let reactionContextNode = self.reactionContextNode {
|
||||||
|
if hasFirstResponder(reactionContextNode.view) {
|
||||||
|
currentHasFirstResponder = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||||
|
if inputPanelView.hasFirstResponder() {
|
||||||
|
currentHasFirstResponder = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var keyboardHeight = component.deviceMetrics.standardInputHeight(inLandscape: false)
|
var keyboardHeight = component.deviceMetrics.standardInputHeight(inLandscape: false)
|
||||||
let keyboardWasHidden = self.inputPanelExternalState.isKeyboardHidden
|
let keyboardWasHidden = self.inputPanelExternalState.isKeyboardHidden
|
||||||
let inputNodeVisible = self.sendMessageContext.currentInputMode == .media || hasFirstResponder(self)
|
let inputNodeVisible = self.sendMessageContext.currentInputMode == .media || currentHasFirstResponder
|
||||||
self.inputPanel.parentState = state
|
self.inputPanel.parentState = state
|
||||||
let inputPanelSize = self.inputPanel.update(
|
var inputPanelSize: CGSize?
|
||||||
transition: inputPanelTransition,
|
|
||||||
component: AnyComponent(MessageInputPanelComponent(
|
let startTime23 = CFAbsoluteTimeGetCurrent()
|
||||||
externalState: self.inputPanelExternalState,
|
|
||||||
context: component.context,
|
if component.slice.peer.id != component.context.account.peerId {
|
||||||
theme: component.theme,
|
inputPanelSize = self.inputPanel.update(
|
||||||
strings: component.strings,
|
transition: inputPanelTransition,
|
||||||
style: .story,
|
component: AnyComponent(MessageInputPanelComponent(
|
||||||
placeholder: inputPlaceholder,
|
externalState: self.inputPanelExternalState,
|
||||||
maxLength: 4096,
|
context: component.context,
|
||||||
queryTypes: [.mention, .emoji],
|
theme: component.theme,
|
||||||
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
strings: component.strings,
|
||||||
resetInputContents: resetInputContents,
|
style: .story,
|
||||||
nextInputMode: { [weak self] hasText in
|
placeholder: inputPlaceholder,
|
||||||
if case .media = self?.sendMessageContext.currentInputMode {
|
maxLength: 4096,
|
||||||
return .text
|
queryTypes: [.mention, .emoji],
|
||||||
} else {
|
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
||||||
return hasText ? .emoji : .stickers
|
resetInputContents: resetInputContents,
|
||||||
}
|
nextInputMode: { [weak self] hasText in
|
||||||
},
|
if case .media = self?.sendMessageContext.currentInputMode {
|
||||||
areVoiceMessagesAvailable: component.slice.additionalPeerData.areVoiceMessagesAvailable,
|
return .text
|
||||||
presentController: { [weak self] c in
|
} else {
|
||||||
guard let self, let component = self.component else {
|
return hasText ? .emoji : .stickers
|
||||||
return
|
}
|
||||||
}
|
},
|
||||||
component.presentController(c, nil)
|
areVoiceMessagesAvailable: component.slice.additionalPeerData.areVoiceMessagesAvailable,
|
||||||
},
|
presentController: { [weak self] c in
|
||||||
presentInGlobalOverlay: { [weak self] c in
|
guard let self, let component = self.component else {
|
||||||
guard let self, let component = self.component else {
|
return
|
||||||
return
|
}
|
||||||
}
|
component.presentController(c, nil)
|
||||||
component.presentInGlobalOverlay(c, nil)
|
},
|
||||||
},
|
presentInGlobalOverlay: { [weak self] c in
|
||||||
sendMessageAction: { [weak self] in
|
guard let self, let component = self.component else {
|
||||||
guard let self else {
|
return
|
||||||
return
|
}
|
||||||
}
|
component.presentInGlobalOverlay(c, nil)
|
||||||
self.sendMessageContext.performSendMessageAction(view: self)
|
},
|
||||||
},
|
sendMessageAction: { [weak self] in
|
||||||
sendMessageOptionsAction: { [weak self] sourceView, gesture in
|
guard let self else {
|
||||||
guard let self else {
|
return
|
||||||
return
|
}
|
||||||
}
|
self.sendMessageContext.performSendMessageAction(view: self)
|
||||||
self.sendMessageContext.presentSendMessageOptions(view: self, sourceView: sourceView, gesture: gesture)
|
},
|
||||||
},
|
sendMessageOptionsAction: { [weak self] sourceView, gesture in
|
||||||
sendStickerAction: { [weak self] sticker in
|
guard let self else {
|
||||||
guard let self else {
|
return
|
||||||
return
|
}
|
||||||
}
|
self.sendMessageContext.presentSendMessageOptions(view: self, sourceView: sourceView, gesture: gesture)
|
||||||
self.sendMessageContext.performSendStickerAction(view: self, fileReference: .standalone(media: sticker))
|
},
|
||||||
},
|
sendStickerAction: { [weak self] sticker in
|
||||||
setMediaRecordingActive: { [weak self] isActive, isVideo, sendAction in
|
guard let self else {
|
||||||
guard let self else {
|
return
|
||||||
return
|
}
|
||||||
}
|
self.sendMessageContext.performSendStickerAction(view: self, fileReference: .standalone(media: sticker))
|
||||||
self.sendMessageContext.setMediaRecordingActive(view: self, isActive: isActive, isVideo: isVideo, sendAction: sendAction)
|
},
|
||||||
},
|
setMediaRecordingActive: { [weak self] isActive, isVideo, sendAction in
|
||||||
lockMediaRecording: { [weak self] in
|
guard let self else {
|
||||||
guard let self else {
|
return
|
||||||
return
|
}
|
||||||
}
|
self.sendMessageContext.setMediaRecordingActive(view: self, isActive: isActive, isVideo: isVideo, sendAction: sendAction)
|
||||||
self.sendMessageContext.lockMediaRecording()
|
},
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
lockMediaRecording: { [weak self] in
|
||||||
},
|
guard let self else {
|
||||||
stopAndPreviewMediaRecording: { [weak self] in
|
return
|
||||||
guard let self else {
|
}
|
||||||
return
|
self.sendMessageContext.lockMediaRecording()
|
||||||
}
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||||
self.sendMessageContext.stopMediaRecording(view: self)
|
},
|
||||||
},
|
stopAndPreviewMediaRecording: { [weak self] in
|
||||||
discardMediaRecordingPreview: { [weak self] in
|
guard let self else {
|
||||||
guard let self else {
|
return
|
||||||
return
|
}
|
||||||
}
|
self.sendMessageContext.stopMediaRecording(view: self)
|
||||||
self.sendMessageContext.videoRecorderValue?.dismissVideo()
|
},
|
||||||
self.sendMessageContext.discardMediaRecordingPreview(view: self)
|
discardMediaRecordingPreview: { [weak self] in
|
||||||
},
|
guard let self else {
|
||||||
attachmentAction: component.slice.peer.isService ? nil : { [weak self] in
|
return
|
||||||
guard let self else {
|
}
|
||||||
return
|
self.sendMessageContext.videoRecorderValue?.dismissVideo()
|
||||||
}
|
self.sendMessageContext.discardMediaRecordingPreview(view: self)
|
||||||
self.sendMessageContext.presentAttachmentMenu(view: self, subject: .default)
|
},
|
||||||
},
|
attachmentAction: component.slice.peer.isService ? nil : { [weak self] in
|
||||||
myReaction: component.slice.item.storyItem.myReaction.flatMap { value -> MessageInputPanelComponent.MyReaction? in
|
guard let self else {
|
||||||
var centerAnimation: TelegramMediaFile?
|
return
|
||||||
var animationFileId: Int64?
|
}
|
||||||
|
self.sendMessageContext.presentAttachmentMenu(view: self, subject: .default)
|
||||||
switch value {
|
},
|
||||||
case .builtin:
|
myReaction: component.slice.item.storyItem.myReaction.flatMap { value -> MessageInputPanelComponent.MyReaction? in
|
||||||
if let availableReactions = component.availableReactions {
|
var centerAnimation: TelegramMediaFile?
|
||||||
for availableReaction in availableReactions.reactionItems {
|
var animationFileId: Int64?
|
||||||
if availableReaction.reaction.rawValue == value {
|
|
||||||
centerAnimation = availableReaction.listAnimation
|
switch value {
|
||||||
break
|
case .builtin:
|
||||||
|
if let availableReactions = component.availableReactions {
|
||||||
|
for availableReaction in availableReactions.reactionItems {
|
||||||
|
if availableReaction.reaction.rawValue == value {
|
||||||
|
centerAnimation = availableReaction.listAnimation
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case let .custom(fileId):
|
||||||
|
animationFileId = fileId
|
||||||
}
|
}
|
||||||
case let .custom(fileId):
|
|
||||||
animationFileId = fileId
|
if animationFileId == nil && centerAnimation == nil {
|
||||||
}
|
return nil
|
||||||
|
|
||||||
if animationFileId == nil && centerAnimation == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return MessageInputPanelComponent.MyReaction(reaction: value, file: centerAnimation, animationFileId: animationFileId)
|
|
||||||
},
|
|
||||||
likeAction: component.slice.peer.isService ? nil : { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.performLikeAction()
|
|
||||||
},
|
|
||||||
likeOptionsAction: component.slice.peer.isService ? nil : { [weak self] sourceView, gesture in
|
|
||||||
gesture?.cancel()
|
|
||||||
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.performLikeOptionsAction(sourceView: sourceView)
|
|
||||||
},
|
|
||||||
inputModeAction: { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.sendMessageContext.toggleInputMode()
|
|
||||||
if !hasFirstResponder(self) {
|
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
|
||||||
} else {
|
|
||||||
self.state?.updated(transition: .immediate)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
timeoutAction: nil,
|
|
||||||
forwardAction: component.slice.item.storyItem.isPublic ? { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.sendMessageContext.performShareAction(view: self)
|
|
||||||
} : nil,
|
|
||||||
moreAction: { [weak self] sourceView, gesture in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.performMoreAction(sourceView: sourceView, gesture: gesture)
|
|
||||||
},
|
|
||||||
presentVoiceMessagesUnavailableTooltip: { [weak self] view in
|
|
||||||
guard let self, let component = self.component, self.voiceMessagesRestrictedTooltipController == nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let rect = view.convert(view.bounds, to: nil)
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let text = presentationData.strings.Conversation_VoiceMessagesRestricted(component.slice.peer.compactDisplayTitle).string
|
|
||||||
let controller = TooltipController(content: .text(text), baseFontSize: presentationData.listsFontSize.baseDisplaySize, padding: 2.0)
|
|
||||||
controller.dismissed = { [weak self] _ in
|
|
||||||
if let self {
|
|
||||||
self.voiceMessagesRestrictedTooltipController = nil
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
component.presentController(controller, TooltipControllerPresentationArguments(sourceViewAndRect: { [weak self] in
|
return MessageInputPanelComponent.MyReaction(reaction: value, file: centerAnimation, animationFileId: animationFileId)
|
||||||
if let self {
|
},
|
||||||
return (self, rect)
|
likeAction: component.slice.peer.isService ? nil : { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return nil
|
self.performLikeAction()
|
||||||
}))
|
},
|
||||||
self.voiceMessagesRestrictedTooltipController = controller
|
likeOptionsAction: component.slice.peer.isService ? nil : { [weak self] sourceView, gesture in
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
|
gesture?.cancel()
|
||||||
},
|
|
||||||
presentTextLengthLimitTooltip: nil,
|
guard let self else {
|
||||||
presentTextFormattingTooltip: nil,
|
return
|
||||||
paste: { [weak self] data in
|
}
|
||||||
guard let self else {
|
self.performLikeOptionsAction(sourceView: sourceView)
|
||||||
return
|
},
|
||||||
}
|
inputModeAction: { [weak self] in
|
||||||
switch data {
|
guard let self else {
|
||||||
case let .images(images):
|
return
|
||||||
self.sendMessageContext.presentMediaPasteboard(view: self, subjects: images.map { .image($0) })
|
}
|
||||||
case let .video(data):
|
self.sendMessageContext.toggleInputMode()
|
||||||
let tempFilePath = NSTemporaryDirectory() + "\(Int64.random(in: 0...Int64.max)).mp4"
|
if !hasFirstResponder(self) {
|
||||||
let url = NSURL(fileURLWithPath: tempFilePath) as URL
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
try? data.write(to: url)
|
} else {
|
||||||
self.sendMessageContext.presentMediaPasteboard(view: self, subjects: [.video(url)])
|
self.state?.updated(transition: .immediate)
|
||||||
case let .gif(data):
|
}
|
||||||
self.sendMessageContext.enqueueGifData(view: self, data: data)
|
},
|
||||||
case let .sticker(image, isMemoji):
|
timeoutAction: nil,
|
||||||
self.sendMessageContext.enqueueStickerImage(view: self, image: image, isMemoji: isMemoji)
|
forwardAction: component.slice.item.storyItem.isPublic ? { [weak self] in
|
||||||
case .text:
|
guard let self else {
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
},
|
self.sendMessageContext.performShareAction(view: self)
|
||||||
audioRecorder: self.sendMessageContext.audioRecorderValue,
|
} : nil,
|
||||||
videoRecordingStatus: !self.sendMessageContext.hasRecordedVideoPreview ? self.sendMessageContext.videoRecorderValue?.audioStatus : nil,
|
moreAction: { [weak self] sourceView, gesture in
|
||||||
isRecordingLocked: self.sendMessageContext.isMediaRecordingLocked,
|
guard let self else {
|
||||||
recordedAudioPreview: self.sendMessageContext.recordedAudioPreview,
|
return
|
||||||
hasRecordedVideoPreview: self.sendMessageContext.hasRecordedVideoPreview,
|
}
|
||||||
wasRecordingDismissed: self.sendMessageContext.wasRecordingDismissed,
|
self.performMoreAction(sourceView: sourceView, gesture: gesture)
|
||||||
timeoutValue: nil,
|
},
|
||||||
timeoutSelected: false,
|
presentVoiceMessagesUnavailableTooltip: { [weak self] view in
|
||||||
displayGradient: false,
|
guard let self, let component = self.component, self.voiceMessagesRestrictedTooltipController == nil else {
|
||||||
bottomInset: component.inputHeight != 0.0 || inputNodeVisible ? 0.0 : bottomContentInset,
|
return
|
||||||
isFormattingLocked: false,
|
}
|
||||||
hideKeyboard: self.sendMessageContext.currentInputMode == .media,
|
let rect = view.convert(view.bounds, to: nil)
|
||||||
forceIsEditing: self.sendMessageContext.currentInputMode == .media,
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
disabledPlaceholder: disabledPlaceholder,
|
let text = presentationData.strings.Conversation_VoiceMessagesRestricted(component.slice.peer.compactDisplayTitle).string
|
||||||
storyId: component.slice.item.storyItem.id
|
let controller = TooltipController(content: .text(text), baseFontSize: presentationData.listsFontSize.baseDisplaySize, padding: 2.0)
|
||||||
)),
|
controller.dismissed = { [weak self] _ in
|
||||||
environment: {},
|
if let self {
|
||||||
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)
|
self.voiceMessagesRestrictedTooltipController = nil
|
||||||
)
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
component.presentController(controller, TooltipControllerPresentationArguments(sourceViewAndRect: { [weak self] in
|
||||||
|
if let self {
|
||||||
|
return (self, rect)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
self.voiceMessagesRestrictedTooltipController = controller
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
|
||||||
|
},
|
||||||
|
presentTextLengthLimitTooltip: nil,
|
||||||
|
presentTextFormattingTooltip: nil,
|
||||||
|
paste: { [weak self] data in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch data {
|
||||||
|
case let .images(images):
|
||||||
|
self.sendMessageContext.presentMediaPasteboard(view: self, subjects: images.map { .image($0) })
|
||||||
|
case let .video(data):
|
||||||
|
let tempFilePath = NSTemporaryDirectory() + "\(Int64.random(in: 0...Int64.max)).mp4"
|
||||||
|
let url = NSURL(fileURLWithPath: tempFilePath) as URL
|
||||||
|
try? data.write(to: url)
|
||||||
|
self.sendMessageContext.presentMediaPasteboard(view: self, subjects: [.video(url)])
|
||||||
|
case let .gif(data):
|
||||||
|
self.sendMessageContext.enqueueGifData(view: self, data: data)
|
||||||
|
case let .sticker(image, isMemoji):
|
||||||
|
self.sendMessageContext.enqueueStickerImage(view: self, image: image, isMemoji: isMemoji)
|
||||||
|
case .text:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
audioRecorder: self.sendMessageContext.audioRecorderValue,
|
||||||
|
videoRecordingStatus: !self.sendMessageContext.hasRecordedVideoPreview ? self.sendMessageContext.videoRecorderValue?.audioStatus : nil,
|
||||||
|
isRecordingLocked: self.sendMessageContext.isMediaRecordingLocked,
|
||||||
|
recordedAudioPreview: self.sendMessageContext.recordedAudioPreview,
|
||||||
|
hasRecordedVideoPreview: self.sendMessageContext.hasRecordedVideoPreview,
|
||||||
|
wasRecordingDismissed: self.sendMessageContext.wasRecordingDismissed,
|
||||||
|
timeoutValue: nil,
|
||||||
|
timeoutSelected: false,
|
||||||
|
displayGradient: false,
|
||||||
|
bottomInset: component.inputHeight != 0.0 || inputNodeVisible ? 0.0 : bottomContentInset,
|
||||||
|
isFormattingLocked: false,
|
||||||
|
hideKeyboard: self.sendMessageContext.currentInputMode == .media,
|
||||||
|
forceIsEditing: self.sendMessageContext.currentInputMode == .media,
|
||||||
|
disabledPlaceholder: disabledPlaceholder,
|
||||||
|
storyId: component.slice.item.storyItem.id
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let startTime3 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
var inputPanelInset: CGFloat = component.containerInsets.bottom
|
var inputPanelInset: CGFloat = component.containerInsets.bottom
|
||||||
var inputHeight = component.inputHeight
|
var inputHeight = component.inputHeight
|
||||||
@ -2816,7 +2844,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
inputPanelBottomInset = bottomContentInset
|
inputPanelBottomInset = bottomContentInset
|
||||||
if case .regular = component.metrics.widthClass {
|
if case .regular = component.metrics.widthClass {
|
||||||
bottomContentInset += 60.0
|
bottomContentInset += 60.0
|
||||||
} else {
|
} else if let inputPanelSize {
|
||||||
bottomContentInset += inputPanelSize.height
|
bottomContentInset += inputPanelSize.height
|
||||||
}
|
}
|
||||||
inputPanelIsOverlay = false
|
inputPanelIsOverlay = false
|
||||||
@ -2832,6 +2860,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let minimizedHeight = max(100.0, availableSize.height - (325.0 + 12.0))
|
let minimizedHeight = max(100.0, availableSize.height - (325.0 + 12.0))
|
||||||
let defaultHeight = 60.0 + component.safeInsets.bottom + 1.0
|
let defaultHeight = 60.0 + component.safeInsets.bottom + 1.0
|
||||||
|
|
||||||
|
let startTime4 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
var validViewListIds: [Int32] = []
|
var validViewListIds: [Int32] = []
|
||||||
if component.slice.peer.id == component.context.account.peerId, let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
|
if component.slice.peer.id == component.context.account.peerId, let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
|
||||||
var visibleViewListIds: [Int32] = [component.slice.item.storyItem.id]
|
var visibleViewListIds: [Int32] = [component.slice.item.storyItem.id]
|
||||||
@ -3310,6 +3340,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.viewLists.removeValue(forKey: id)
|
self.viewLists.removeValue(forKey: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let startTime5 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
let itemSize = CGSize(width: availableSize.width, height: ceil(availableSize.width * 1.77778))
|
let itemSize = CGSize(width: availableSize.width, height: ceil(availableSize.width * 1.77778))
|
||||||
let contentDefaultBottomInset: CGFloat = bottomContentInset
|
let contentDefaultBottomInset: CGFloat = bottomContentInset
|
||||||
|
|
||||||
@ -3465,6 +3497,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let startTime6 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
let soundButtonState = isSilentVideo || component.isAudioMuted
|
let soundButtonState = isSilentVideo || component.isAudioMuted
|
||||||
let soundButtonSize = self.soundButton.update(
|
let soundButtonSize = self.soundButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@ -3748,29 +3782,35 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let startTime7 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
let topGradientHeight: CGFloat = 90.0
|
let topGradientHeight: CGFloat = 90.0
|
||||||
let topContentGradientRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: topGradientHeight))
|
let topContentGradientRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: topGradientHeight))
|
||||||
transition.setPosition(view: self.topContentGradientView, position: topContentGradientRect.center)
|
transition.setPosition(view: self.topContentGradientView, position: topContentGradientRect.center)
|
||||||
transition.setBounds(view: self.topContentGradientView, bounds: CGRect(origin: CGPoint(), size: topContentGradientRect.size))
|
transition.setBounds(view: self.topContentGradientView, bounds: CGRect(origin: CGPoint(), size: topContentGradientRect.size))
|
||||||
|
|
||||||
let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize)
|
var inputPanelFrameValue: CGRect?
|
||||||
var inputPanelAlpha: CGFloat = component.slice.peer.id == component.context.account.peerId || component.hideUI || self.isEditingStory ? 0.0 : 1.0
|
if let inputPanelSize {
|
||||||
if case .regular = component.metrics.widthClass {
|
let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize)
|
||||||
inputPanelAlpha *= component.visibilityFraction
|
inputPanelFrameValue = inputPanelFrame
|
||||||
}
|
var inputPanelAlpha: CGFloat = component.slice.peer.id == component.context.account.peerId || component.hideUI || self.isEditingStory ? 0.0 : 1.0
|
||||||
if let inputPanelView = self.inputPanel.view {
|
if case .regular = component.metrics.widthClass {
|
||||||
if inputPanelView.superview == nil {
|
inputPanelAlpha *= component.visibilityFraction
|
||||||
self.componentContainerView.addSubview(inputPanelView)
|
|
||||||
}
|
}
|
||||||
|
if let inputPanelView = self.inputPanel.view {
|
||||||
var inputPanelOffset: CGFloat = 0.0
|
if inputPanelView.superview == nil {
|
||||||
if component.slice.peer.id != component.context.account.peerId && !self.inputPanelExternalState.isEditing {
|
self.componentContainerView.addSubview(inputPanelView)
|
||||||
let bandingOffset = scrollingRubberBandingOffset(offset: verticalPanFraction * availableSize.height, bandingStart: 0.0, range: 10.0)
|
}
|
||||||
inputPanelOffset = -max(0.0, min(10.0, bandingOffset))
|
|
||||||
|
var inputPanelOffset: CGFloat = 0.0
|
||||||
|
if component.slice.peer.id != component.context.account.peerId && !self.inputPanelExternalState.isEditing {
|
||||||
|
let bandingOffset = scrollingRubberBandingOffset(offset: verticalPanFraction * availableSize.height, bandingStart: 0.0, range: 10.0)
|
||||||
|
inputPanelOffset = -max(0.0, min(10.0, bandingOffset))
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPanelTransition.setFrame(view: inputPanelView, frame: inputPanelFrame.offsetBy(dx: 0.0, dy: inputPanelOffset))
|
||||||
|
transition.setAlpha(view: inputPanelView, alpha: inputPanelAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputPanelTransition.setFrame(view: inputPanelView, frame: inputPanelFrame.offsetBy(dx: 0.0, dy: inputPanelOffset))
|
|
||||||
transition.setAlpha(view: inputPanelView, alpha: inputPanelAlpha)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let captionItem = self.captionItem, captionItem.itemId != component.slice.item.storyItem.id {
|
if let captionItem = self.captionItem, captionItem.itemId != component.slice.item.storyItem.id {
|
||||||
@ -3915,7 +3955,11 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
likeRect.origin.x -= 30.0
|
likeRect.origin.x -= 30.0
|
||||||
reactionsAnchorRect = likeRect
|
reactionsAnchorRect = likeRect
|
||||||
} else {
|
} else {
|
||||||
reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrame.maxX - 40.0, y: inputPanelFrame.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
|
if let inputPanelFrameValue {
|
||||||
|
reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrameValue.maxX - 40.0, y: inputPanelFrameValue.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
|
||||||
|
} else {
|
||||||
|
reactionsAnchorRect = CGRect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var effectiveDisplayReactions = false
|
var effectiveDisplayReactions = false
|
||||||
@ -4297,7 +4341,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bottomGradientHeight = inputPanelSize.height + 32.0
|
let bottomGradientHeight = (inputPanelSize?.height ?? 0.0) + 32.0
|
||||||
transition.setFrame(layer: self.bottomContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: availableSize.height - inputHeight - bottomGradientHeight), size: CGSize(width: contentFrame.width, height: bottomGradientHeight)))
|
transition.setFrame(layer: self.bottomContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: availableSize.height - inputHeight - bottomGradientHeight), size: CGSize(width: contentFrame.width, height: bottomGradientHeight)))
|
||||||
//transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0)
|
//transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0)
|
||||||
transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: 0.0)
|
transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: 0.0)
|
||||||
@ -4354,10 +4398,14 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let startTime8 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
self.ignoreScrolling = false
|
self.ignoreScrolling = false
|
||||||
|
|
||||||
self.updateScrolling(transition: itemsTransition)
|
self.updateScrolling(transition: itemsTransition)
|
||||||
|
|
||||||
|
let startTime9 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
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
|
||||||
let navigationStripTopInset: CGFloat = 8.0
|
let navigationStripTopInset: CGFloat = 8.0
|
||||||
@ -4396,8 +4444,27 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
component.externalState.derivedMediaSize = contentFrame.size
|
component.externalState.derivedMediaSize = contentFrame.size
|
||||||
if component.slice.peer.id == component.context.account.peerId {
|
if component.slice.peer.id == component.context.account.peerId {
|
||||||
component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY
|
component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY
|
||||||
|
} else if let inputPanelFrameValue {
|
||||||
|
component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrameValue.minY, contentFrame.maxY)
|
||||||
} else {
|
} else {
|
||||||
component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrame.minY, contentFrame.maxY)
|
component.externalState.derivedBottomInset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if !"".isEmpty {
|
||||||
|
print("inner update time:\n" +
|
||||||
|
" 1: \((CFAbsoluteTimeGetCurrent() - startTime1) * 1000.0) ms\n" +
|
||||||
|
" 2: \((CFAbsoluteTimeGetCurrent() - startTime2) * 1000.0) ms\n" +
|
||||||
|
" 2.1: \((CFAbsoluteTimeGetCurrent() - startTime21) * 1000.0) ms\n" +
|
||||||
|
" 2.2: \((CFAbsoluteTimeGetCurrent() - startTime22) * 1000.0) ms\n" +
|
||||||
|
" 2.3: \((CFAbsoluteTimeGetCurrent() - startTime23) * 1000.0) ms\n" +
|
||||||
|
" 3: \((CFAbsoluteTimeGetCurrent() - startTime3) * 1000.0) ms\n" +
|
||||||
|
" 4: \((CFAbsoluteTimeGetCurrent() - startTime4) * 1000.0) ms\n" +
|
||||||
|
" 5: \((CFAbsoluteTimeGetCurrent() - startTime5) * 1000.0) ms\n" +
|
||||||
|
" 6: \((CFAbsoluteTimeGetCurrent() - startTime6) * 1000.0) ms\n" +
|
||||||
|
" 7: \((CFAbsoluteTimeGetCurrent() - startTime7) * 1000.0) ms\n" +
|
||||||
|
" 8: \((CFAbsoluteTimeGetCurrent() - startTime8) * 1000.0) ms\n" +
|
||||||
|
" 9: \((CFAbsoluteTimeGetCurrent() - startTime9) * 1000.0) ms\n"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentSize
|
return contentSize
|
||||||
|
@ -380,7 +380,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let actualBounds = self.scrollView.bounds
|
let actualBounds = self.scrollView.bounds
|
||||||
let visibleBounds = actualBounds.insetBy(dx: 0.0, dy: -200.0)
|
let visibleBounds = actualBounds//.insetBy(dx: 0.0, dy: -200.0)
|
||||||
|
|
||||||
var synchronousLoad = false
|
var synchronousLoad = false
|
||||||
if let hint = transition.userData(PeerListItemComponent.TransitionHint.self) {
|
if let hint = transition.userData(PeerListItemComponent.TransitionHint.self) {
|
||||||
@ -402,6 +402,12 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*if "".isEmpty {
|
||||||
|
if index > range.lowerBound - 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
let itemFrame = itemLayout.itemFrame(for: index)
|
let itemFrame = itemLayout.itemFrame(for: index)
|
||||||
|
|
||||||
if index >= viewListState.items.count {
|
if index >= viewListState.items.count {
|
||||||
|
@ -264,6 +264,10 @@ public final class TextFieldComponent: Component {
|
|||||||
self.updateEntities()
|
self.updateEntities()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func hasFirstResponder() -> Bool {
|
||||||
|
return self.textView.isFirstResponder
|
||||||
|
}
|
||||||
|
|
||||||
public func insertText(_ text: NSAttributedString) {
|
public func insertText(_ text: NSAttributedString) {
|
||||||
self.updateInputState { state in
|
self.updateInputState { state in
|
||||||
return state.insertText(text)
|
return state.insertText(text)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user