mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Story fixes
This commit is contained in:
parent
b47a8191c8
commit
34c1682545
@ -649,7 +649,7 @@ public final class PeerInfoNavigationSourceTag {
|
||||
}
|
||||
|
||||
public protocol PeerInfoScreen: ViewController {
|
||||
|
||||
var peerId: PeerId { get }
|
||||
}
|
||||
|
||||
public protocol ChatController: ViewController {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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>] = [
|
||||
|
@ -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
|
||||
}()
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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: {},
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -24,6 +24,7 @@ swift_library(
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/Components/HierarchyTrackingLayer",
|
||||
"//submodules/TelegramUI/Components/ChatListTitleView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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({})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user