Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-07-10 04:00:59 +02:00
commit d145699ef9
23 changed files with 399 additions and 101 deletions

View File

@ -649,7 +649,7 @@ public final class PeerInfoNavigationSourceTag {
}
public protocol PeerInfoScreen: ViewController {
var peerId: PeerId { get }
}
public protocol ChatController: ViewController {

View File

@ -2177,7 +2177,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
#endif
if let lockViewFrame = self.findTitleView()?.lockViewFrame, !self.didShowPasscodeLockTooltipController {
if let componentView = self.chatListHeaderView(), let storyPeerListView = componentView.storyPeerListView(), let _ = storyPeerListView.lockViewFrame(), !self.didShowPasscodeLockTooltipController {
self.passcodeLockTooltipDisposable.set(combineLatest(queue: .mainQueue(), ApplicationSpecificNotice.getPasscodeLockTips(accountManager: self.context.sharedContext.accountManager), self.context.sharedContext.accountManager.accessChallengeData() |> take(1)).start(next: { [weak self] tooltipValue, passcodeView in
if let strongSelf = self {
if !tooltipValue {
@ -2187,8 +2187,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.DialogList_PasscodeLockHelp), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, dismissByTapOutside: true)
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { [weak self] in
if let strongSelf = self, let titleView = strongSelf.findTitleView() {
return (titleView, lockViewFrame.offsetBy(dx: 4.0, dy: 14.0))
if let self, let componentView = self.chatListHeaderView(), let storyPeerListView = componentView.storyPeerListView(), let lockViewFrame = storyPeerListView.lockViewFrame() {
return (storyPeerListView, lockViewFrame.offsetBy(dx: 4.0, dy: 14.0))
}
return nil
}))
@ -5087,8 +5087,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
public var lockViewFrame: CGRect? {
if let titleView = self.findTitleView(), let lockViewFrame = titleView.lockViewFrame {
return titleView.convert(lockViewFrame, to: self.view)
if let componentView = self.chatListHeaderView(), let storyPeerListView = componentView.storyPeerListView(), let lockViewFrame = storyPeerListView.lockViewFrame() {
return storyPeerListView.convert(lockViewFrame, to: self.view)
} else {
return nil
}
@ -5465,7 +5465,35 @@ private final class ChatListLocationContext {
#elseif DEBUG && false
networkState = .single(AccountNetworkState.connecting(proxy: nil))
#else
networkState = context.account.networkState
let realNetworkState = context.account.networkState
networkState = Signal { subscriber in
let currentValue = Atomic<AccountNetworkState?>(value: nil)
let disposable = (realNetworkState
|> mapToSignal { value -> Signal<AccountNetworkState, NoError> in
let previousValue = currentValue.swap(value)
if let previousValue {
switch value {
case .waitingForNetwork, .connecting, .updating:
if case .online = previousValue {
return .single(value) |> delay(0.3, queue: .mainQueue())
} else {
return .single(value)
}
default:
return .single(value)
}
} else {
return .single(value)
}
}
|> deliverOnMainQueue).start(next: { value in
subscriber.putNext(value)
})
return ActionDisposable {
disposable.dispose()
}
}
#endif
switch location {

View File

@ -6,6 +6,14 @@ open class HighlightTrackingButton: UIButton {
public var internalHighligthedChanged: (Bool) -> Void = { _ in }
public var highligthedChanged: (Bool) -> Void = { _ in }
override public init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
if !self.internalHighlighted {
self.internalHighlighted = true

View File

@ -1148,7 +1148,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.textBackgroundNode.isHidden = true
if let accessoryComponentView = node.accessoryComponentView {
/*if let accessoryComponentView = node.accessoryComponentView {
let tempContainer = UIView()
let accessorySize = accessoryComponentView.bounds.size
@ -1161,14 +1161,15 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
accessoryComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
tempContainer.addSubview(accessoryComponentView)
self.view.addSubview(tempContainer)
}
}*/
self.textBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak node] _ in
textBackgroundCompleted = true
intermediateCompletion()
if let node = node, let accessoryComponentContainer = node.accessoryComponentContainer, let accessoryComponentView = node.accessoryComponentView {
accessoryComponentContainer.addSubview(accessoryComponentView)
if let node = node, let accessoryComponentView = node.accessoryComponentView {
//accessoryComponentContainer.addSubview(accessoryComponentView)
accessoryComponentView.layer.animateAlpha(from: 0.0, to: accessoryComponentView.alpha, duration: 0.2)
}
})

View File

@ -246,7 +246,9 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
}
if let accessoryComponentContainer = strongSelf.accessoryComponentContainer {
accessoryComponentContainer.frame = CGRect(origin: CGPoint(x: constrainedSize.width - accessoryComponentContainer.bounds.width - 4.0, y: floor((constrainedSize.height - accessoryComponentContainer.bounds.height) / 2.0)), size: accessoryComponentContainer.bounds.size)
accessoryComponentContainer.frame = CGRect(origin: CGPoint(x: constrainedSize.width - accessoryComponentContainer.bounds.width - 4.0, y: floor((constrainedSize.height * expansionProgress - accessoryComponentContainer.bounds.height) / 2.0)), size: accessoryComponentContainer.bounds.size)
transition.updateAlpha(layer: accessoryComponentContainer.layer, alpha: innerAlpha)
}
}
})

View File

@ -1157,6 +1157,7 @@ public class Account {
self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(network: self.network, postbox: self.postbox, isRemove: false).start())
self.managedOperationsDisposable.add(managedAutoexpireStoryOperations(network: self.network, postbox: self.postbox).start())
self.managedOperationsDisposable.add(managedPeerTimestampAttributeOperations(network: self.network, postbox: self.postbox).start())
self.managedOperationsDisposable.add(managedSynchronizeViewStoriesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
let extractedExpr: [Signal<AccountRunningImportantTasks, NoError>] = [

View File

@ -196,6 +196,7 @@ private var declaredEncodables: Void = {
declareEncodable(TranslationMessageAttribute.self, f: { TranslationMessageAttribute(decoder: $0) })
declareEncodable(SynchronizeAutosaveItemOperation.self, f: { SynchronizeAutosaveItemOperation(decoder: $0) })
declareEncodable(TelegramMediaStory.self, f: { TelegramMediaStory(decoder: $0) })
declareEncodable(SynchronizeViewStoriesOperation.self, f: { SynchronizeViewStoriesOperation(decoder: $0) })
return
}()

View File

@ -0,0 +1,132 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
private final class ManagedSynchronizeViewStoriesOperationsHelper {
var operationDisposables: [PeerId: (Int32, Disposable)] = [:]
func update(_ entries: [PeerMergedOperationLogEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) {
var disposeOperations: [Disposable] = []
var beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)] = []
var validPeerIds: [PeerId] = []
for entry in entries {
guard let operation = entry.contents as? SynchronizeViewStoriesOperation else {
continue
}
validPeerIds.append(entry.peerId)
var replace = true
if let current = self.operationDisposables[entry.peerId] {
if current.0 != operation.storyId {
disposeOperations.append(current.1)
replace = true
}
} else {
replace = true
}
if replace {
let disposable = MetaDisposable()
self.operationDisposables[entry.peerId] = (operation.storyId, disposable)
beginOperations.append((entry, disposable))
}
}
var removedPeerIds: [PeerId] = []
for (peerId, info) in self.operationDisposables {
if !validPeerIds.contains(peerId) {
removedPeerIds.append(peerId)
disposeOperations.append(info.1)
}
}
for peerId in removedPeerIds {
self.operationDisposables.removeValue(forKey: peerId)
}
return (disposeOperations, beginOperations)
}
func reset() -> [Disposable] {
let disposables = Array(self.operationDisposables.values.map(\.1))
self.operationDisposables.removeAll()
return disposables
}
}
private func withTakenOperation(postbox: Postbox, peerId: PeerId, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal<Void, NoError>) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> in
var result: PeerMergedOperationLogEntry?
transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SynchronizeViewStories, tagLocalIndex: tagLocalIndex, { entry in
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeViewStoriesOperation {
result = entry.mergedEntry!
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
} else {
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
}
})
return f(transaction, result)
} |> switchToLatest
}
func managedSynchronizeViewStoriesOperations(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
return Signal { _ in
let helper = Atomic<ManagedSynchronizeViewStoriesOperationsHelper>(value: ManagedSynchronizeViewStoriesOperationsHelper())
let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SynchronizeViewStories, limit: 10).start(next: { view in
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in
return helper.update(view.entries)
}
for disposable in disposeOperations {
disposable.dispose()
}
for (entry, disposable) in beginOperations {
let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Void, NoError> in
if let entry = entry {
if let operation = entry.contents as? SynchronizeViewStoriesOperation {
if let peer = transaction.getPeer(entry.peerId) {
return pushStoriesAreSeen(postbox: postbox, network: network, stateManager: stateManager, peer: peer, operation: operation)
} else {
return .complete()
}
} else {
assertionFailure()
}
}
return .complete()
})
|> then(postbox.transaction { transaction -> Void in
let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: OperationLogTags.SynchronizeViewStories, tagLocalIndex: entry.tagLocalIndex)
})
disposable.set(signal.start())
}
})
return ActionDisposable {
let disposables = helper.with { helper -> [Disposable] in
return helper.reset()
}
for disposable in disposables {
disposable.dispose()
}
disposable.dispose()
}
}
}
private func pushStoriesAreSeen(postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: SynchronizeViewStoriesOperation) -> Signal<Void, NoError> {
guard let inputPeer = apiInputUser(peer) else {
return .complete()
}
return network.request(Api.functions.stories.readStories(userId: inputPeer, maxId: operation.storyId))
|> `catch` { _ -> Signal<[Int32], NoError> in
return .single([])
}
|> map { _ -> Void in
return Void()
}
}

View File

@ -185,6 +185,7 @@ public struct OperationLogTags {
public static let SynchronizeMarkAllUnseenReactions = PeerOperationLogTag(value: 21)
public static let SynchronizeInstalledEmoji = PeerOperationLogTag(value: 22)
public static let SynchronizeAutosaveItems = PeerOperationLogTag(value: 23)
public static let SynchronizeViewStories = PeerOperationLogTag(value: 24)
}
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {

View File

@ -0,0 +1,46 @@
import Foundation
import Postbox
public final class SynchronizeViewStoriesOperation: PostboxCoding {
public let peerId: PeerId
public let storyId: Int32
public init(peerId: PeerId, storyId: Int32) {
self.peerId = peerId
self.storyId = storyId
}
public init(decoder: PostboxDecoder) {
self.peerId = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
self.storyId = decoder.decodeInt32ForKey("s", orElse: 0)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.peerId.toInt64(), forKey: "p")
encoder.encodeInt32(self.storyId, forKey: "s")
}
}
func _internal_addSynchronizeViewStoriesOperation(peerId: PeerId, storyId: Int32, transaction: Transaction) {
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeViewStories
var topOperation: (SynchronizeViewStoriesOperation, Int32)?
transaction.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in
if let operation = entry.contents as? SynchronizeViewStoriesOperation {
topOperation = (operation, entry.tagLocalIndex)
}
return false
})
var replace = false
if let (topOperation, topLocalIndex) = topOperation {
if topOperation.storyId < storyId {
let _ = transaction.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: topLocalIndex)
}
replace = true
} else {
replace = true
}
if replace {
transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeViewStoriesOperation(peerId: peerId, storyId: storyId))
}
}

View File

@ -5,7 +5,7 @@ import TelegramApi
public enum EngineStoryInputMedia {
case image(dimensions: PixelDimensions, data: Data, stickers: [TelegramMediaFile])
case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameImageData: Data?, stickers: [TelegramMediaFile])
case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameFile: TempBoxFile?, stickers: [TelegramMediaFile])
var embeddedStickers: [TelegramMediaFile] {
switch self {
@ -602,11 +602,10 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput
flags: []
)
return imageMedia
case let .video(dimensions, duration, resource, firstFrameImageData, _):
case let .video(dimensions, duration, resource, firstFrameFile, _):
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if let firstFrameImageData = firstFrameImageData {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
account.postbox.mediaBox.storeResourceData(resource.id, data: firstFrameImageData)
if let firstFrameFile = firstFrameFile {
account.postbox.mediaBox.storeCachedResourceRepresentation(resource.id.stringRepresentation, representationId: "first-frame", keepDuration: .general, tempFile: firstFrameFile)
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
}
@ -1147,26 +1146,20 @@ func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32, asPi
).postboxRepresentation)
}
_internal_addSynchronizeViewStoriesOperation(peerId: peerId, storyId: id, transaction: transaction)
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
|> mapToSignal { inputUser -> Signal<Never, NoError> in
guard let inputUser = inputUser else {
return .complete()
}
|> mapToSignal { _ -> Signal<Never, NoError> in
account.stateManager.injectStoryUpdates(updates: [.read(peerId: peerId, maxId: id)])
#if DEBUG && false
if "".isEmpty {
return .complete()
}
#endif
return .complete()
return account.network.request(Api.functions.stories.readStories(userId: inputUser, maxId: id))
/*return account.network.request(Api.functions.stories.readStories(userId: inputUser, maxId: id))
|> `catch` { _ -> Signal<[Int32], NoError> in
return .single([])
}
|> ignoreValues
|> ignoreValues*/
}
}
}

View File

@ -952,6 +952,12 @@ public final class ChatListHeaderComponent: Component {
return
}
self.component?.openStatusSetup(sourceView)
},
lockAction: { [weak self] in
guard let self else {
return
}
self.component?.toggleIsLocked()
}
)),
environment: {},

View File

@ -336,7 +336,7 @@ public final class ChatListNavigationBar: Component {
storiesUnlocked = false
}
if allowAvatarsExpansion && transition.animation.isImmediate {
if allowAvatarsExpansion, transition.animation.isImmediate, let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty {
if self.storiesUnlocked != storiesUnlocked {
if storiesUnlocked {
HapticFeedback().tap()

View File

@ -3,11 +3,13 @@ import UIKit
import Display
import TelegramPresentationData
final class ChatListTitleLockView: UIView {
public final class ChatListTitleLockView: UIView {
private let topView: UIImageView
private let bottomView: UIImageView
override init(frame: CGRect) {
private var theme: PresentationTheme?
override public init(frame: CGRect) {
self.topView = UIImageView()
self.bottomView = UIImageView()
@ -21,10 +23,14 @@ final class ChatListTitleLockView: UIView {
fatalError("init(coder:) has not been implemented")
}
func updateTheme(_ theme: PresentationTheme) {
self.topView.image = PresentationResourcesChatList.lockTopUnlockedImage(theme)
self.bottomView.image = PresentationResourcesChatList.lockBottomUnlockedImage(theme)
self.layoutItems()
public func updateTheme(_ theme: PresentationTheme) {
if self.theme !== theme {
self.theme = theme
self.topView.image = PresentationResourcesChatList.lockTopUnlockedImage(theme)
self.bottomView.image = PresentationResourcesChatList.lockBottomUnlockedImage(theme)
self.layoutItems()
}
}
private func layoutItems() {
@ -32,7 +38,7 @@ final class ChatListTitleLockView: UIView {
self.bottomView.frame = CGRect(x: 0.0, y: 6.0, width: 10.0, height: 8.0)
}
override func layoutSubviews() {
override public func layoutSubviews() {
super.layoutSubviews()
self.layoutItems()

View File

@ -177,6 +177,7 @@ public final class PeerListItemComponent: Component {
self.separatorLayer = SimpleLayer()
self.containerButton = HighlightTrackingButton()
self.containerButton.isExclusiveTouch = true
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = false

View File

@ -1083,6 +1083,9 @@ private final class StoryContainerScreenComponent: Component {
if self.isHoldingTouch {
isProgressPaused = true
}
if !environment.isVisible {
isProgressPaused = true
}
var dismissPanOffset: CGFloat = 0.0
var dismissPanScale: CGFloat = 1.0

View File

@ -135,7 +135,7 @@ final class StoryItemContentComponent: Component {
manager: component.context.sharedContext.mediaManager.universalVideoManager,
decoration: StoryVideoDecoration(),
content: NativeVideoContent(
id: .message(0, file.fileId),
id: .contextResult(0, "\(UInt64.random(in: 0 ... UInt64.max))"),
userLocation: .other,
fileReference: .story(peer: peerReference, id: component.item.id, media: file),
imageReference: nil,

View File

@ -2470,7 +2470,7 @@ public final class StoryItemSetContainerComponent: Component {
}
})),
environment: {},
containerSize: CGSize(width: contentFrame.width, height: 44.0)
containerSize: CGSize(width: max(10.0, contentFrame.width - headerRightOffset), height: 44.0)
)
if let view = currentCenterInfoItem.view.view {
var animateIn = false
@ -3194,20 +3194,28 @@ public final class StoryItemSetContainerComponent: Component {
}
}))
} else {
guard let chatController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
if "".isEmpty {
navigationController.pushViewController(chatController)
} else {
var viewControllers = navigationController.viewControllers
if let index = viewControllers.firstIndex(where: { $0 === controller }) {
viewControllers.insert(chatController, at: index)
} else {
viewControllers.append(chatController)
var currentViewControllers = navigationController.viewControllers
if let index = currentViewControllers.firstIndex(where: { c in
if let c = c as? PeerInfoScreen, c.peerId == peer.id {
return true
}
navigationController.setViewControllers(viewControllers, animated: true)
return false
}) {
if let controller = component.controller() as? StoryContainerScreen {
controller.dismiss()
}
for i in (index + 1 ..< currentViewControllers.count).reversed() {
if currentViewControllers[i] !== component.controller() {
currentViewControllers.remove(at: i)
}
}
navigationController.setViewControllers(currentViewControllers, animated: true)
} else {
guard let chatController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
navigationController.pushViewController(chatController)
}
}
}
@ -3402,7 +3410,16 @@ public final class StoryItemSetContainerComponent: Component {
}
let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let _ = (context.engine.messages.editStory(id: id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: firstFrameImageData, stickers: stickers), text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
let firstFrameFile = firstFrameImageData.flatMap { data -> TempBoxFile? in
let file = TempBox.shared.tempFile(fileName: "image.jpg")
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
return file
} else {
return nil
}
}
let _ = (context.engine.messages.editStory(id: id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return

View File

@ -24,6 +24,7 @@ swift_library(
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/Components/HierarchyTrackingLayer",
"//submodules/TelegramUI/Components/ChatListTitleView",
],
visibility = [
"//visibility:public",

View File

@ -11,32 +11,7 @@ import SwiftSignalKit
import TelegramPresentationData
import StoryContainerScreen
import EmojiStatusComponent
private func solveParabolicMotion(from sourcePoint: CGPoint, to targetPosition: CGPoint, progress: CGFloat) -> CGPoint {
if sourcePoint.y == targetPosition.y {
return sourcePoint.interpolate(to: targetPosition, amount: progress)
}
//(x - h)² + (y - k)² = r²
//(x1 - h) * (x1 - h) + (y1 - k) * (y1 - k) = r * r
//(x2 - h) * (x2 - h) + (y2 - k) * (y2 - k) = r * r
let x1 = sourcePoint.y
let y1 = sourcePoint.x
let x2 = targetPosition.y
let y2 = targetPosition.x
let b = (x1 * x1 * y2 - x2 * x2 * y1) / (x1 * x1 - x2 * x2)
let k = (y1 - y2) / (x1 * x1 - x2 * x2)
let x = sourcePoint.y.interpolate(to: targetPosition.y, amount: progress)
let y = k * x * x + b
return CGPoint(x: y, y: x)
}
private let modelSpringAnimation: CABasicAnimation = {
return makeSpringBounceAnimation("", 0.0, 88.0)
}()
import ChatListTitleView
public final class StoryPeerListComponent: Component {
public enum PeerStatus: Equatable {
@ -84,6 +59,7 @@ public final class StoryPeerListComponent: Component {
public let peerAction: (EnginePeer?) -> Void
public let contextPeerAction: (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void
public let openStatusSetup: (UIView) -> Void
public let lockAction: () -> Void
public init(
externalState: ExternalState,
@ -104,7 +80,8 @@ public final class StoryPeerListComponent: Component {
uploadProgress: Float?,
peerAction: @escaping (EnginePeer?) -> Void,
contextPeerAction: @escaping (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void,
openStatusSetup: @escaping (UIView) -> Void
openStatusSetup: @escaping (UIView) -> Void,
lockAction: @escaping () -> Void
) {
self.externalState = externalState
self.context = context
@ -125,6 +102,7 @@ public final class StoryPeerListComponent: Component {
self.peerAction = peerAction
self.contextPeerAction = contextPeerAction
self.openStatusSetup = openStatusSetup
self.lockAction = lockAction
}
public static func ==(lhs: StoryPeerListComponent, rhs: StoryPeerListComponent) -> Bool {
@ -217,14 +195,17 @@ public final class StoryPeerListComponent: Component {
func frame(at index: Int) -> CGRect {
if self.itemCount <= 1 {
return CGRect(origin: CGPoint(x: floor((self.containerSize.width - self.itemSize.width) * 0.5), y: self.containerInsets.top), size: self.itemSize)
return CGRect(origin: CGPoint(x: self.containerInsets.left + floor((self.containerSize.width - self.containerInsets.left - containerInsets.right - self.itemSize.width) * 0.5), y: self.containerInsets.top), size: self.itemSize)
} else if self.contentSize.width < self.containerSize.width {
let usableWidth = self.containerSize.width - self.containerInsets.left - self.containerInsets.right
let usableSpacingWidth = usableWidth - self.itemSize.width * CGFloat(self.itemCount)
var spacing = floor(usableSpacingWidth / CGFloat(self.itemCount + 1))
spacing = min(120.0, spacing)
return CGRect(origin: CGPoint(x: self.containerInsets.left + spacing + (self.itemSize.width + spacing) * CGFloat(index), y: self.containerInsets.top), size: self.itemSize)
spacing = min(100.0, spacing)
let contentWidth = self.itemSize.width * CGFloat(self.itemCount) + spacing * CGFloat(max(0, self.itemCount - 1))
return CGRect(origin: CGPoint(x: floor((self.containerSize.width - contentWidth) * 0.5) + (self.itemSize.width + spacing) * CGFloat(index), y: self.containerInsets.top), size: self.itemSize)
} else {
return CGRect(origin: CGPoint(x: self.containerInsets.left + (self.itemSize.width + self.itemSpacing) * CGFloat(index), y: self.containerInsets.top), size: self.itemSize)
}
@ -338,6 +319,8 @@ public final class StoryPeerListComponent: Component {
private var titleIndicatorView: ComponentView<Empty>?
private var titleLockView: ChatListTitleLockView?
private var titleLockButton: HighlightTrackingButton?
private let titleView: UIImageView
private var titleState: TitleState?
private var titleViewAnimation: TitleAnimationState?
@ -429,6 +412,15 @@ public final class StoryPeerListComponent: Component {
component.peerAction(nil)
}
@objc private func titleLockButtonPressed() {
guard let component = self.component else {
return
}
if component.titleHasLock {
component.lockAction()
}
}
public func setPreviewedItem(signal: Signal<StoryId?, NoError>) {
self.previewedItemDisposable?.dispose()
self.previewedItemDisposable = (signal |> map(\.?.peerId) |> distinctUntilChanged |> deliverOnMainQueue).start(next: { [weak self] itemId in
@ -460,6 +452,14 @@ public final class StoryPeerListComponent: Component {
return self.titleView.frame
}
public func lockViewFrame() -> CGRect? {
if let titleLockView = self.titleLockView {
return titleLockView.frame
} else {
return nil
}
}
public func transitionViewForItem(peerId: EnginePeer.Id) -> (UIView, StoryContainerScreen.TransitionView)? {
if self.collapsedButton.isUserInteractionEnabled {
return nil
@ -507,6 +507,12 @@ public final class StoryPeerListComponent: Component {
let titleSize = self.titleView.image?.size ?? CGSize()
realTitleContentWidth += titleSize.width
var titleLockOffset: CGFloat = 0.0
if component.titleHasLock {
titleLockOffset = 20.0
}
realTitleContentWidth += titleLockOffset
var titleIconSize: CGSize?
if let peerStatus = component.titlePeerStatus {
let statusContent: EmojiStatusComponent.Content
@ -911,13 +917,8 @@ public final class StoryPeerListComponent: Component {
let totalCount: Int
let unseenCount: Int
if peer.id == component.context.account.peerId {
totalCount = 1
unseenCount = itemSet.unseenCount != 0 ? 1 : 0
} else {
totalCount = itemSet.storyCount
unseenCount = itemSet.unseenCount
}
totalCount = itemSet.storyCount
unseenCount = itemSet.unseenCount
let _ = visibleItem.view.update(
transition: itemTransition,
@ -1192,7 +1193,7 @@ public final class StoryPeerListComponent: Component {
titleIndicatorView.alpha = indicatorAlpha
}
let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset, y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize)
let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset + titleLockOffset, y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize)
if let image = self.titleView.image {
self.titleView.center = CGPoint(x: titleFrame.minX, y: titleFrame.midY)
self.titleView.bounds = CGRect(origin: CGPoint(), size: image.size)
@ -1210,6 +1211,38 @@ public final class StoryPeerListComponent: Component {
self.titleView.layer.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
}
if component.titleHasLock {
let titleLockView: ChatListTitleLockView
if let current = self.titleLockView {
titleLockView = current
} else {
titleLockView = ChatListTitleLockView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)))
self.titleLockView = titleLockView
self.addSubview(titleLockView)
}
titleLockView.updateTheme(component.theme)
let lockFrame = CGRect(x: titleFrame.minX - 6.0 - 12.0, y: titleFrame.minY + 3.0, width: 2.0, height: 2.0)
titleLockView.frame = lockFrame
let titleLockButton: HighlightTrackingButton
if let current = self.titleLockButton {
titleLockButton = current
} else {
titleLockButton = HighlightTrackingButton()
self.titleLockButton = titleLockButton
self.addSubview(titleLockButton)
titleLockButton.addTarget(self, action: #selector(self.titleLockButtonPressed), for: .touchUpInside)
}
titleLockButton.frame = CGRect(origin: CGPoint(x: lockFrame.minX - 4.0, y: titleFrame.minY - 4.0), size: CGSize(width: titleFrame.maxX - lockFrame.minX + 4.0, height: titleFrame.height + 8.0))
} else if let titleLockView = self.titleLockView {
self.titleLockView = nil
titleLockView.removeFromSuperview()
self.titleLockButton?.removeFromSuperview()
self.titleLockButton = nil
}
for disappearingTitleView in self.disappearingTitleViews {
if let image = disappearingTitleView.imageView.image {
disappearingTitleView.imageView.center = CGPoint(x: titleFrame.minX, y: titleFrame.midY)
@ -1243,6 +1276,8 @@ public final class StoryPeerListComponent: Component {
}
titleContentOffset += collapsedState.titleWidth
self.scrollContainerView.isUserInteractionEnabled = collapsedState.maxFraction >= 1.0 - 0.001
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -1256,6 +1291,12 @@ public final class StoryPeerListComponent: Component {
}
}
if let titleLockButton = self.titleLockButton {
if let result = titleLockButton.hitTest(self.convert(point, to: titleLockButton), with: event) {
return result
}
}
var result: UIView?
for view in self.subviews.reversed() {
if let resultValue = view.hitTest(self.convert(point, to: view), with: event), resultValue.isUserInteractionEnabled {

View File

@ -576,14 +576,6 @@ public final class StoryPeerListItemComponent: Component {
func update(component: StoryPeerListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let size = availableSize
transition.setFrame(view: self.button, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: self.extractedBackgroundView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -4.0, dy: -4.0))
self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: self.extractedBackgroundView.frame.minX - 2.0, y: self.extractedBackgroundView.frame.minY), size: CGSize(width: self.extractedBackgroundView.frame.width + 4.0, height: self.extractedBackgroundView.frame.height))
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
let themeUpdated = self.component?.theme !== component.theme
let previousComponent = self.component
@ -877,6 +869,15 @@ public final class StoryPeerListItemComponent: Component {
}
}
let extractedBackgroundWidth = max(size.width + 8.0, titleSize.width + 10.0)
transition.setFrame(view: self.button, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: self.extractedBackgroundView, frame: CGRect(origin: CGPoint(x: floor((size.width - extractedBackgroundWidth) * 0.5), y: -4.0), size: CGSize(width: extractedBackgroundWidth, height: size.height + 8.0)))
self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: self.extractedBackgroundView.frame.minX - 2.0, y: self.extractedBackgroundView.frame.minY), size: CGSize(width: self.extractedBackgroundView.frame.width + 4.0, height: self.extractedBackgroundView.frame.height))
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
return availableSize
}
}

View File

@ -9834,7 +9834,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortcutResponder {
private let context: AccountContext
fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private let peerId: PeerId
public let peerId: PeerId
private let avatarInitiallyExpanded: Bool
private let isOpenedFromChat: Bool
private let nearbyPeerDistance: Int32?

View File

@ -396,8 +396,17 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
}
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let firstFrameFile = imageData.flatMap { data -> TempBoxFile? in
let file = TempBox.shared.tempFile(fileName: "image.jpg")
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
return file
} else {
return nil
}
}
let entities = generateChatInputTextEntities(caption)
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch {
commit({})
}