mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-15 18:59:54 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
f49a6f63f3
@ -128,8 +128,9 @@ private final class PhoneAndCountryNode: ASDisplayNode {
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.processNumberChange(number: strongSelf.phoneInputNode.number)
|
||||
|
||||
if strongSelf.hasCountry {
|
||||
strongSelf.hasNumberUpdated?(!strongSelf.phoneInputNode.codeAndNumber.2.isEmpty)
|
||||
let isServiceNumber = strongSelf.phoneInputNode.number.hasPrefix("+999")
|
||||
if strongSelf.hasCountry || isServiceNumber {
|
||||
strongSelf.hasNumberUpdated?(!strongSelf.phoneInputNode.codeAndNumber.2.isEmpty || isServiceNumber)
|
||||
} else {
|
||||
strongSelf.hasNumberUpdated?(false)
|
||||
}
|
||||
|
||||
@ -1243,7 +1243,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
super.init()
|
||||
|
||||
self.useMainQueueTransactions = true
|
||||
//self.useMainQueueTransactions = true
|
||||
|
||||
self.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor
|
||||
self.verticalScrollIndicatorFollowsOverscroll = true
|
||||
|
||||
@ -840,8 +840,8 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
self.controlsContainerNode.addSubnode(self.stripContainerNode)
|
||||
self.controlsClippingNode.addSubnode(self.controlsContainerNode)
|
||||
self.controlsClippingOffsetNode.addSubnode(self.controlsClippingNode)
|
||||
self.stripContainerNode.addSubnode(self.setByYouNode)
|
||||
self.stripContainerNode.addSubnode(self.setByYouImageNode)
|
||||
self.addSubnode(self.setByYouNode)
|
||||
self.addSubnode(self.setByYouImageNode)
|
||||
|
||||
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
@ -1435,7 +1435,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
transition.updateAlpha(node: self.setByYouNode, alpha: 0.7)
|
||||
self.setByYouNode.attributedText = NSAttributedString(string: photoTitle, font: Font.regular(12.0), textColor: UIColor.white)
|
||||
let setByYouSize = self.setByYouNode.updateLayout(size)
|
||||
self.setByYouNode.frame = CGRect(origin: CGPoint(x: size.width - setByYouSize.width - 14.0, y: size.height - setByYouSize.height - 58.0), size: setByYouSize)
|
||||
self.setByYouNode.frame = CGRect(origin: CGPoint(x: size.width - setByYouSize.width - 14.0, y: size.height - setByYouSize.height - 18.0), size: setByYouSize)
|
||||
self.setByYouNode.isUserInteractionEnabled = hasLink
|
||||
} else {
|
||||
transition.updateAlpha(node: self.setByYouNode, alpha: 0.0)
|
||||
|
||||
@ -284,6 +284,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
public var isReactionSearchActive: Bool = false
|
||||
|
||||
public var reduceMotion: Bool = false
|
||||
|
||||
public static func randomGenericReactionEffect(context: AccountContext) -> Signal<String?, NoError> {
|
||||
return context.engine.stickers.loadedStickerPack(reference: .emojiGenericAnimations, forceActualized: false)
|
||||
|> map { result -> [TelegramMediaFile]? in
|
||||
@ -907,7 +909,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
if animateIn {
|
||||
itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion)
|
||||
itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion && !self.reduceMotion)
|
||||
}
|
||||
|
||||
if self.getEmojiContent != nil, i == itemLayout.visibleItemCount - 1, let itemNode = itemNode as? ReactionNode {
|
||||
@ -1197,7 +1199,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if let animateInFromAnchorRect = animateInFromAnchorRect {
|
||||
if let animateInFromAnchorRect = animateInFromAnchorRect, !self.reduceMotion {
|
||||
let springDuration: Double = 0.5
|
||||
let springDamping: CGFloat = 104.0
|
||||
let springScaleDelay: Double = 0.1
|
||||
@ -1606,11 +1608,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let mainCircleDelay: Double = 0.01
|
||||
|
||||
self.backgroundNode.animateIn()
|
||||
if !self.presentationData.reduceMotion && !self.reduceMotion {
|
||||
self.backgroundNode.animateIn()
|
||||
}
|
||||
|
||||
self.didAnimateIn = true
|
||||
|
||||
if !self.presentationData.reduceMotion {
|
||||
if !self.presentationData.reduceMotion && !self.reduceMotion {
|
||||
for i in 0 ..< self.items.count {
|
||||
guard let itemNode = self.visibleItemNodes[i] else {
|
||||
continue
|
||||
|
||||
@ -215,6 +215,7 @@ private final class FetchImpl {
|
||||
private var requiredRangesDisposable: Disposable?
|
||||
private var requiredRanges: [RequiredRange] = []
|
||||
|
||||
private let defaultPartSize: Int64
|
||||
private var state: State?
|
||||
|
||||
private let loggingIdentifier: String
|
||||
@ -263,6 +264,24 @@ private final class FetchImpl {
|
||||
self.updatedFileReference = Data()
|
||||
#endif*/
|
||||
|
||||
var isStory = false
|
||||
if let info = parameters?.info as? TelegramCloudMediaResourceFetchInfo {
|
||||
switch info.reference {
|
||||
case let .media(media, _):
|
||||
if case .story = media {
|
||||
isStory = true
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isStory {
|
||||
self.defaultPartSize = 512 * 1024
|
||||
} else {
|
||||
self.defaultPartSize = 128 * 1024
|
||||
}
|
||||
|
||||
if let resource = resource as? TelegramCloudMediaResource {
|
||||
if let apiInputLocation = resource.apiInputLocation(fileReference: Data()) {
|
||||
self.loggingIdentifier = "\(apiInputLocation)"
|
||||
@ -297,7 +316,7 @@ private final class FetchImpl {
|
||||
|
||||
self.state = .fetching(FetchingState(
|
||||
fetchLocation: .datacenter(self.datacenterId),
|
||||
partSize: 128 * 1024,
|
||||
partSize: self.defaultPartSize,
|
||||
minPartSize: 4 * 1024,
|
||||
maxPartSize: 1 * 1024 * 1024,
|
||||
partAlignment: 4 * 1024,
|
||||
@ -451,9 +470,9 @@ private final class FetchImpl {
|
||||
}
|
||||
self.state = .fetching(FetchImpl.FetchingState(
|
||||
fetchLocation: .cdn(cdnData),
|
||||
partSize: 128 * 1024,
|
||||
partSize: self.defaultPartSize,
|
||||
minPartSize: 4 * 1024,
|
||||
maxPartSize: 128 * 1024,
|
||||
maxPartSize: self.defaultPartSize,
|
||||
partAlignment: 4 * 1024,
|
||||
partDivision: 1 * 1024 * 1024,
|
||||
maxPendingParts: 6
|
||||
@ -499,9 +518,9 @@ private final class FetchImpl {
|
||||
|
||||
self.state = .fetching(FetchingState(
|
||||
fetchLocation: fetchLocation,
|
||||
partSize: 128 * 1024,
|
||||
partSize: self.defaultPartSize,
|
||||
minPartSize: 4 * 1024,
|
||||
maxPartSize: 128 * 1024,
|
||||
maxPartSize: self.defaultPartSize,
|
||||
partAlignment: 4 * 1024,
|
||||
partDivision: 1 * 1024 * 1024,
|
||||
maxPendingParts: 6
|
||||
@ -686,9 +705,9 @@ private final class FetchImpl {
|
||||
case let .cdnRedirect(cdnData):
|
||||
self.state = .fetching(FetchImpl.FetchingState(
|
||||
fetchLocation: .cdn(cdnData),
|
||||
partSize: 128 * 1024,
|
||||
partSize: self.defaultPartSize,
|
||||
minPartSize: 4 * 1024,
|
||||
maxPartSize: 128 * 1024,
|
||||
maxPartSize: self.defaultPartSize,
|
||||
partAlignment: 4 * 1024,
|
||||
partDivision: 1 * 1024 * 1024,
|
||||
maxPendingParts: 6
|
||||
|
||||
@ -513,7 +513,23 @@ private final class MultipartFetchManager {
|
||||
self.useMainConnection = useMainConnection
|
||||
|
||||
self.completeSize = size
|
||||
if let size = size {
|
||||
|
||||
var isStory = false
|
||||
if let info = parameters?.info as? TelegramCloudMediaResourceFetchInfo {
|
||||
switch info.reference {
|
||||
case let .media(media, _):
|
||||
if case .story = media {
|
||||
isStory = true
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isStory {
|
||||
self.defaultPartSize = 512 * 1024
|
||||
self.parallelParts = 4
|
||||
} else if let size = size {
|
||||
if size <= 512 * 1024 {
|
||||
self.defaultPartSize = 16 * 1024
|
||||
self.parallelParts = 4 * 4
|
||||
|
||||
@ -466,17 +466,18 @@ public final class PeerStoryListContext {
|
||||
self.peerId = peerId
|
||||
self.isArchived = isArchived
|
||||
|
||||
self.stateValue = State(peerReference: nil, items: [], totalCount: 0, loadMoreToken: 0, isCached: true)
|
||||
self.stateValue = State(peerReference: nil, items: [], totalCount: 0, loadMoreToken: 0, isCached: true, allEntityFiles: [:])
|
||||
|
||||
let _ = (account.postbox.transaction { transaction -> (PeerReference?, [EngineStoryItem], Int) in
|
||||
let _ = (account.postbox.transaction { transaction -> (PeerReference?, [EngineStoryItem], Int, [MediaId: TelegramMediaFile]) in
|
||||
let key = ValueBoxKey(length: 8 + 1)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
|
||||
guard let cached = cached else {
|
||||
return (nil, [], 0)
|
||||
return (nil, [], 0, [:])
|
||||
}
|
||||
var items: [EngineStoryItem] = []
|
||||
var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||
for storedItem in cached.items {
|
||||
if case let .item(item) = storedItem, let media = item.media {
|
||||
let mappedItem = EngineStoryItem(
|
||||
@ -504,19 +505,30 @@ public final class PeerStoryListContext {
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
items.append(mappedItem)
|
||||
|
||||
for entity in mappedItem.entities {
|
||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
if allEntityFiles[mediaId] == nil {
|
||||
if let file = transaction.getMedia(mediaId) as? TelegramMediaFile {
|
||||
allEntityFiles[file.fileId] = file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let peerReference = transaction.getPeer(peerId).flatMap(PeerReference.init)
|
||||
|
||||
return (peerReference, items, Int(cached.totalCount))
|
||||
return (peerReference, items, Int(cached.totalCount), allEntityFiles)
|
||||
}
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] peerReference, items, totalCount in
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] peerReference, items, totalCount, allEntityFiles in
|
||||
guard let `self` = self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.stateValue = State(peerReference: peerReference, items: items, totalCount: totalCount, loadMoreToken: 0, isCached: true)
|
||||
self.stateValue = State(peerReference: peerReference, items: items, totalCount: totalCount, loadMoreToken: 0, isCached: true, allEntityFiles: allEntityFiles)
|
||||
self.loadMore()
|
||||
})
|
||||
}
|
||||
@ -828,19 +840,22 @@ public final class PeerStoryListContext {
|
||||
public var totalCount: Int
|
||||
public var loadMoreToken: Int?
|
||||
public var isCached: Bool
|
||||
public var allEntityFiles: [MediaId: TelegramMediaFile]
|
||||
|
||||
init(
|
||||
peerReference: PeerReference?,
|
||||
items: [EngineStoryItem],
|
||||
totalCount: Int,
|
||||
loadMoreToken: Int?,
|
||||
isCached: Bool
|
||||
isCached: Bool,
|
||||
allEntityFiles: [MediaId: TelegramMediaFile]
|
||||
) {
|
||||
self.peerReference = peerReference
|
||||
self.items = items
|
||||
self.totalCount = totalCount
|
||||
self.loadMoreToken = loadMoreToken
|
||||
self.isCached = isCached
|
||||
self.allEntityFiles = allEntityFiles
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -410,7 +410,7 @@ public final class ChatListHeaderComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
func updateNavigationTransitionAsNext(previousView: ContentView, fraction: CGFloat, transition: Transition, completion: @escaping () -> Void) {
|
||||
func updateNavigationTransitionAsNext(previousView: ContentView, storyPeerListView: StoryPeerListComponent.View?, fraction: CGFloat, transition: Transition, completion: @escaping () -> Void) {
|
||||
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.titleOffsetContainer.bounds.size), completion: { _ in
|
||||
completion()
|
||||
})
|
||||
@ -420,7 +420,15 @@ public final class ChatListHeaderComponent: Component {
|
||||
transition.setScale(view: backButtonView.arrowView, scale: pow(max(0.001, fraction), 2.0))
|
||||
transition.setAlpha(view: backButtonView.arrowView, alpha: pow(fraction, 2.0))
|
||||
|
||||
if let previousChatListTitleView = previousView.chatListTitleView {
|
||||
if let storyPeerListView {
|
||||
let previousTitleFrame = storyPeerListView.titleFrame()
|
||||
let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self)
|
||||
|
||||
let totalOffset = previousTitleFrame.minX - backButtonTitleFrame.minX
|
||||
|
||||
transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size))
|
||||
transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0))
|
||||
} else if let previousChatListTitleView = previousView.chatListTitleView {
|
||||
let previousTitleFrame = previousChatListTitleView.titleNode.view.convert(previousChatListTitleView.titleNode.bounds, to: previousView.titleOffsetContainer)
|
||||
let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self)
|
||||
|
||||
@ -993,7 +1001,7 @@ public final class ChatListHeaderComponent: Component {
|
||||
secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: 0.0, transition: .immediate, completion: {})
|
||||
} else {
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: .immediate, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, fraction: 0.0, transition: .immediate, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: 0.0, transition: .immediate, completion: {})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1002,7 +1010,7 @@ public final class ChatListHeaderComponent: Component {
|
||||
secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
} else {
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
}
|
||||
}
|
||||
} else if let secondaryContentView = self.secondaryContentView {
|
||||
@ -1016,7 +1024,7 @@ public final class ChatListHeaderComponent: Component {
|
||||
})
|
||||
} else {
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: transition, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in
|
||||
secondaryContentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
@ -1025,7 +1033,7 @@ public final class ChatListHeaderComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if let storyPeerList = self.storyPeerList, let storyPeerListComponentView = storyPeerList.view {
|
||||
if let storyPeerList = self.storyPeerList, let storyPeerListComponentView = storyPeerList.view as? StoryPeerListComponent.View {
|
||||
if storyPeerListComponentView.superview == nil {
|
||||
self.addSubview(storyPeerListComponentView)
|
||||
}
|
||||
@ -1035,13 +1043,15 @@ public final class ChatListHeaderComponent: Component {
|
||||
|
||||
//let storyPeerListPosition: CGFloat = storyPeerListMinOffset * (1.0 - component.storiesFraction) + storyPeerListMaxOffset * component.storiesFraction
|
||||
|
||||
var defaultStoryListX: CGFloat = 0.0
|
||||
if let primaryContentView = self.primaryContentView {
|
||||
defaultStoryListX = primaryContentView.centerContentOrigin - (self.storyPeerListExternalState.collapsedWidth * 0.5 + 12.0) - availableSize.width * 0.5
|
||||
var storiesX: CGFloat = 0.0
|
||||
if let nextBackButtonView = self.secondaryContentView?.backButtonView {
|
||||
let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: self)
|
||||
let storyListTitleFrame = storyPeerListComponentView.titleFrame()
|
||||
|
||||
storiesX += (backButtonTitleFrame.minX - storyListTitleFrame.minX) * component.secondaryTransition
|
||||
}
|
||||
let _ = defaultStoryListX
|
||||
|
||||
storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: -1.0 * availableSize.width * component.secondaryTransition + 0.0, y: storyPeerListMaxOffset), size: CGSize(width: availableSize.width, height: 79.0)))
|
||||
storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: storiesX, y: storyPeerListMaxOffset), size: CGSize(width: availableSize.width, height: 79.0)))
|
||||
|
||||
let storyListNormalAlpha: CGFloat = 1.0
|
||||
|
||||
|
||||
@ -145,7 +145,7 @@ public final class ChatListNavigationBar: Component {
|
||||
|
||||
public static let searchScrollHeight: CGFloat = 52.0
|
||||
public static let storiesScrollHeight: CGFloat = {
|
||||
return 79.0
|
||||
return 83.0
|
||||
}()
|
||||
|
||||
public final class View: UIView {
|
||||
@ -404,7 +404,7 @@ public final class ChatListNavigationBar: Component {
|
||||
if component.statusBarHeight < 1.0 {
|
||||
headerContentY = 0.0
|
||||
} else {
|
||||
headerContentY = component.statusBarHeight + 12.0
|
||||
headerContentY = component.statusBarHeight + 5.0
|
||||
}
|
||||
}
|
||||
let headerContentFrame = CGRect(origin: CGPoint(x: 0.0, y: headerContentY), size: headerContentSize)
|
||||
@ -579,7 +579,7 @@ public final class ChatListNavigationBar: Component {
|
||||
var contentHeight = component.statusBarHeight
|
||||
|
||||
if component.statusBarHeight >= 1.0 {
|
||||
contentHeight += 10.0
|
||||
contentHeight += 3.0
|
||||
}
|
||||
contentHeight += 44.0
|
||||
|
||||
|
||||
@ -2580,7 +2580,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
|
||||
|
||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? "The story will have no sound." : "The story will have sound."), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? "The story will have no sound" : "The story will have sound"), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
return .ignore
|
||||
})
|
||||
self.muteTooltip = tooltipController
|
||||
|
||||
@ -63,9 +63,11 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
),
|
||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global())
|
||||
)
|
||||
|> mapToSignal { _, views, globalNotificationSettings -> Signal<(CombinedView, [PeerId: Peer], EngineGlobalNotificationSettings), NoError> in
|
||||
return context.account.postbox.transaction { transaction -> (CombinedView, [PeerId: Peer], EngineGlobalNotificationSettings) in
|
||||
|> mapToSignal { _, views, globalNotificationSettings -> Signal<(CombinedView, [PeerId: Peer], EngineGlobalNotificationSettings, [MediaId: TelegramMediaFile]), NoError> in
|
||||
return context.account.postbox.transaction { transaction -> (CombinedView, [PeerId: Peer], EngineGlobalNotificationSettings, [MediaId: TelegramMediaFile]) in
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||
|
||||
if let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView {
|
||||
for item in itemsView.items {
|
||||
if let item = item.value.get(Stories.StoredItem.self), case let .item(itemValue) = item {
|
||||
@ -76,13 +78,24 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
for entity in itemValue.entities {
|
||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
if allEntityFiles[mediaId] == nil {
|
||||
if let file = transaction.getMedia(mediaId) as? TelegramMediaFile {
|
||||
allEntityFiles[file.fileId] = file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (views, peers, globalNotificationSettings)
|
||||
|
||||
return (views, peers, globalNotificationSettings, allEntityFiles)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] views, peers, globalNotificationSettings in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] views, peers, globalNotificationSettings, allEntityFiles in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -257,7 +270,8 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
return StoryContentItem(
|
||||
position: nil,
|
||||
peerId: peer.id,
|
||||
storyItem: item
|
||||
storyItem: item,
|
||||
entityFiles: extractItemEntityFiles(item: item, allEntityFiles: allEntityFiles)
|
||||
)
|
||||
}
|
||||
|
||||
@ -268,7 +282,8 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
item: StoryContentItem(
|
||||
position: mappedFocusedIndex ?? focusedIndex,
|
||||
peerId: peer.id,
|
||||
storyItem: mappedItem
|
||||
storyItem: mappedItem,
|
||||
entityFiles: extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
|
||||
),
|
||||
totalCount: totalCount,
|
||||
previousItemId: previousItemId,
|
||||
@ -907,11 +922,12 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
||||
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId),
|
||||
TelegramEngine.EngineData.Item.NotificationSettings.Global()
|
||||
),
|
||||
context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer]) in
|
||||
context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile]) in
|
||||
guard let item = transaction.getStory(id: storyId)?.get(Stories.StoredItem.self) else {
|
||||
return (nil, [:])
|
||||
return (nil, [:], [:])
|
||||
}
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||
if case let .item(item) = item {
|
||||
if let views = item.views {
|
||||
for id in views.seenPeerIds {
|
||||
@ -920,8 +936,18 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
for entity in item.entities {
|
||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
if allEntityFiles[mediaId] == nil {
|
||||
if let file = transaction.getMedia(mediaId) as? TelegramMediaFile {
|
||||
allEntityFiles[file.fileId] = file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (item, peers)
|
||||
return (item, peers, allEntityFiles)
|
||||
}
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data, itemAndPeers in
|
||||
@ -930,7 +956,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
||||
}
|
||||
|
||||
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
|
||||
let (item, peers) = itemAndPeers
|
||||
let (item, peers, allEntityFiles) = itemAndPeers
|
||||
|
||||
guard let peer else {
|
||||
return
|
||||
@ -981,7 +1007,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
||||
let mainItem = StoryContentItem(
|
||||
position: 0,
|
||||
peerId: peer.id,
|
||||
storyItem: mappedItem
|
||||
storyItem: mappedItem,
|
||||
entityFiles: extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
|
||||
)
|
||||
let stateValue = StoryContentContextState(
|
||||
slice: StoryContentContextState.FocusedSlice(
|
||||
@ -1130,7 +1157,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
||||
return StoryContentItem(
|
||||
position: nil,
|
||||
peerId: peer.id,
|
||||
storyItem: stateItem
|
||||
storyItem: stateItem,
|
||||
entityFiles: extractItemEntityFiles(item: stateItem, allEntityFiles: state.allEntityFiles)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1141,7 +1169,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
||||
item: StoryContentItem(
|
||||
position: nil,
|
||||
peerId: peer.id,
|
||||
storyItem: item
|
||||
storyItem: item,
|
||||
entityFiles: extractItemEntityFiles(item: item, allEntityFiles: state.allEntityFiles)
|
||||
),
|
||||
totalCount: state.totalCount,
|
||||
previousItemId: focusedIndex == 0 ? nil : state.items[focusedIndex - 1].id,
|
||||
@ -1326,3 +1355,16 @@ public func preloadStoryMedia(context: AccountContext, peer: PeerReference, stor
|
||||
|
||||
return combineLatest(signals) |> ignoreValues
|
||||
}
|
||||
|
||||
func extractItemEntityFiles(item: EngineStoryItem, allEntityFiles: [MediaId: TelegramMediaFile]) -> [MediaId: TelegramMediaFile] {
|
||||
var result: [MediaId: TelegramMediaFile] = [:]
|
||||
for entity in item.entities {
|
||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
if let file = allEntityFiles[mediaId] {
|
||||
result[file.fileId] = file
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -1005,7 +1005,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
itemSetContainerInsets.bottom = floorToScreenPixels((availableSize.height - itemSetContainerSize.height) / 2.0)
|
||||
itemSetContainerSafeInsets.bottom = 0.0
|
||||
}
|
||||
|
||||
|
||||
let _ = itemSetView.view.update(
|
||||
transition: itemSetTransition,
|
||||
component: AnyComponent(StoryItemSetContainerComponent(
|
||||
@ -1023,7 +1023,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
deviceMetrics: environment.deviceMetrics,
|
||||
isProgressPaused: isProgressPaused || i != focusedIndex,
|
||||
isAudioMuted: self.audioMode == .off || (self.audioMode == .ambient && !self.isMuteSwitchOn),
|
||||
useAmbientMode: self.audioMode == .ambient,
|
||||
audioMode: self.audioMode,
|
||||
hideUI: (i == focusedIndex && (self.itemSetPanState?.didBegin == false || self.itemSetPinchState != nil)),
|
||||
visibilityFraction: 1.0 - abs(panFraction + cubeAdditionalRotationFraction),
|
||||
isPanning: self.itemSetPanState?.didBegin == true,
|
||||
|
||||
@ -80,15 +80,18 @@ public final class StoryContentItem: Equatable {
|
||||
public let position: Int?
|
||||
public let peerId: EnginePeer.Id?
|
||||
public let storyItem: EngineStoryItem
|
||||
public let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||
|
||||
public init(
|
||||
position: Int?,
|
||||
peerId: EnginePeer.Id?,
|
||||
storyItem: EngineStoryItem
|
||||
storyItem: EngineStoryItem,
|
||||
entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||
) {
|
||||
self.position = position
|
||||
self.peerId = peerId
|
||||
self.storyItem = storyItem
|
||||
self.entityFiles = entityFiles
|
||||
}
|
||||
|
||||
public static func ==(lhs: StoryContentItem, rhs: StoryContentItem) -> Bool {
|
||||
@ -101,6 +104,9 @@ public final class StoryContentItem: Equatable {
|
||||
if lhs.storyItem != rhs.storyItem {
|
||||
return false
|
||||
}
|
||||
if lhs.entityFiles != rhs.entityFiles {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
let context: AccountContext
|
||||
let text: String
|
||||
let entities: [MessageTextEntity]
|
||||
let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||
let action: (Action) -> Void
|
||||
let longTapAction: (Action) -> Void
|
||||
|
||||
@ -51,6 +52,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
context: AccountContext,
|
||||
text: String,
|
||||
entities: [MessageTextEntity],
|
||||
entityFiles: [EngineMedia.Id: TelegramMediaFile],
|
||||
action: @escaping (Action) -> Void,
|
||||
longTapAction: @escaping (Action) -> Void
|
||||
) {
|
||||
@ -58,6 +60,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
self.context = context
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.entityFiles = entityFiles
|
||||
self.action = action
|
||||
self.longTapAction = longTapAction
|
||||
}
|
||||
@ -75,6 +78,9 @@ final class StoryContentCaptionComponent: Component {
|
||||
if lhs.entities != rhs.entities {
|
||||
return false
|
||||
}
|
||||
if lhs.entityFiles != rhs.entityFiles {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -221,7 +227,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
let edgeDistanceFraction = edgeDistance / 7.0
|
||||
transition.setAlpha(view: self.scrollFullMaskView, alpha: 1.0 - edgeDistanceFraction)
|
||||
|
||||
let shadowOverflow: CGFloat = 56.0
|
||||
let shadowOverflow: CGFloat = 58.0
|
||||
let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow))
|
||||
transition.setFrame(layer: self.shadowGradientLayer, frame: shadowFrame)
|
||||
transition.setFrame(layer: self.shadowPlainLayer, frame: CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.maxY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0)))
|
||||
@ -365,7 +371,8 @@ final class StoryContentCaptionComponent: Component {
|
||||
boldItalicFont: Font.semiboldItalic(16.0),
|
||||
fixedFont: Font.monospace(16.0),
|
||||
blockQuoteFont: Font.monospace(16.0),
|
||||
message: nil
|
||||
message: nil,
|
||||
entityFiles: component.entityFiles
|
||||
)
|
||||
|
||||
let makeLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||
@ -485,11 +492,11 @@ final class StoryContentCaptionComponent: Component {
|
||||
var locations: [NSNumber] = []
|
||||
var colors: [CGColor] = []
|
||||
let numStops = 10
|
||||
let baseAlpha: CGFloat = 0.5
|
||||
let baseAlpha: CGFloat = 0.6
|
||||
for i in 0 ..< numStops {
|
||||
let step = 1.0 - CGFloat(i) / CGFloat(numStops - 1)
|
||||
locations.append((1.0 - step) as NSNumber)
|
||||
let alphaStep: CGFloat = pow(step, 1.2)
|
||||
let alphaStep: CGFloat = pow(step, 1.0)
|
||||
colors.append(UIColor.black.withAlphaComponent(alphaStep * baseAlpha).cgColor)
|
||||
}
|
||||
|
||||
|
||||
@ -29,13 +29,13 @@ final class StoryItemContentComponent: Component {
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer
|
||||
let item: EngineStoryItem
|
||||
let useAmbientMode: Bool
|
||||
let audioMode: StoryContentItem.AudioMode
|
||||
|
||||
init(context: AccountContext, peer: EnginePeer, item: EngineStoryItem, useAmbientMode: Bool) {
|
||||
init(context: AccountContext, peer: EnginePeer, item: EngineStoryItem, audioMode: StoryContentItem.AudioMode) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.item = item
|
||||
self.useAmbientMode = useAmbientMode
|
||||
self.audioMode = audioMode
|
||||
}
|
||||
|
||||
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
|
||||
@ -138,8 +138,8 @@ final class StoryItemContentComponent: Component {
|
||||
imageReference: nil,
|
||||
streamVideo: .story,
|
||||
loopVideo: true,
|
||||
enableSound: true,
|
||||
beginWithAmbientSound: component.useAmbientMode,
|
||||
enableSound: component.audioMode != .off,
|
||||
beginWithAmbientSound: component.audioMode == .ambient,
|
||||
mixWithOthers: true,
|
||||
useLargeThumbnail: false,
|
||||
autoFetchFullSizeThumbnail: false,
|
||||
@ -169,12 +169,16 @@ final class StoryItemContentComponent: Component {
|
||||
self.environment?.presentationProgressUpdated(1.0, true)
|
||||
}
|
||||
videoNode.ownsContentNodeUpdated = { [weak self] value in
|
||||
guard let self else {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if value {
|
||||
self.videoNode?.seek(0.0)
|
||||
self.videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: .stop)
|
||||
if component.audioMode != .off {
|
||||
self.videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: .stop)
|
||||
} else {
|
||||
self.videoNode?.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
videoNode.canAttachContent = true
|
||||
|
||||
@ -86,7 +86,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let isProgressPaused: Bool
|
||||
public let isAudioMuted: Bool
|
||||
public let useAmbientMode: Bool
|
||||
public let audioMode: StoryContentItem.AudioMode
|
||||
public let hideUI: Bool
|
||||
public let visibilityFraction: CGFloat
|
||||
public let isPanning: Bool
|
||||
@ -118,7 +118,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
deviceMetrics: DeviceMetrics,
|
||||
isProgressPaused: Bool,
|
||||
isAudioMuted: Bool,
|
||||
useAmbientMode: Bool,
|
||||
audioMode: StoryContentItem.AudioMode,
|
||||
hideUI: Bool,
|
||||
visibilityFraction: CGFloat,
|
||||
isPanning: Bool,
|
||||
@ -149,7 +149,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.isProgressPaused = isProgressPaused
|
||||
self.isAudioMuted = isAudioMuted
|
||||
self.useAmbientMode = useAmbientMode
|
||||
self.audioMode = audioMode
|
||||
self.hideUI = hideUI
|
||||
self.visibilityFraction = visibilityFraction
|
||||
self.isPanning = isPanning
|
||||
@ -201,7 +201,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if lhs.isAudioMuted != rhs.isAudioMuted {
|
||||
return false
|
||||
}
|
||||
if lhs.useAmbientMode != rhs.useAmbientMode {
|
||||
if lhs.audioMode != rhs.audioMode {
|
||||
return false
|
||||
}
|
||||
if lhs.hideUI != rhs.hideUI {
|
||||
@ -1058,7 +1058,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
context: component.context,
|
||||
peer: component.slice.peer,
|
||||
item: item.storyItem,
|
||||
useAmbientMode: component.useAmbientMode
|
||||
audioMode: component.audioMode
|
||||
)),
|
||||
environment: {
|
||||
itemEnvironment
|
||||
@ -1379,6 +1379,9 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if let closeFriendIconView = self.closeFriendIcon?.view {
|
||||
closeFriendIconView.layer.animateAlpha(from: closeFriendIconView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
if let captionView = self.captionItem?.view.view {
|
||||
captionView.layer.animateAlpha(from: captionView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
self.closeButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.topContentGradientLayer.animateAlpha(from: CGFloat(self.topContentGradientLayer.opacity), to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
|
||||
@ -2332,7 +2335,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let tooltipScreen = TooltipScreen(
|
||||
account: component.context.account,
|
||||
sharedContext: component.context.sharedContext,
|
||||
text: .plain(text: tooltipText), style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: self).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .manual, shouldDismissOnTouch: { _, _ in
|
||||
text: .plain(text: tooltipText), style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: self).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: true)
|
||||
}
|
||||
)
|
||||
@ -2527,6 +2530,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
context: component.context,
|
||||
text: component.slice.item.storyItem.text,
|
||||
entities: component.slice.item.storyItem.entities,
|
||||
entityFiles: component.slice.item.entityFiles,
|
||||
action: { [weak self] action in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
@ -2676,7 +2680,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
reactionContextNode.displayTail = false
|
||||
self.reactionContextNode = reactionContextNode
|
||||
|
||||
reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in
|
||||
reactionContextNode.reactionSelected = { [weak self, weak reactionContextNode] updateReaction, _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -2700,23 +2704,24 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
targetView.isUserInteractionEnabled = false
|
||||
self.addSubview(targetView)
|
||||
|
||||
reactionContextNode.willAnimateOutToReaction(value: updateReaction.reaction)
|
||||
reactionContextNode.animateOutToReaction(value: updateReaction.reaction, targetView: targetView, hideNode: false, animateTargetContainer: nil, addStandaloneReactionAnimation: "".isEmpty ? nil : { [weak self] standaloneReactionAnimation in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
standaloneReactionAnimation.frame = self.bounds
|
||||
self.addSubview(standaloneReactionAnimation.view)
|
||||
}, completion: { [weak targetView, weak reactionContextNode] in
|
||||
targetView?.removeFromSuperview()
|
||||
if let reactionContextNode {
|
||||
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
|
||||
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
||||
reactionContextNode?.view.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if let reactionContextNode {
|
||||
reactionContextNode.willAnimateOutToReaction(value: updateReaction.reaction)
|
||||
reactionContextNode.animateOutToReaction(value: updateReaction.reaction, targetView: targetView, hideNode: false, animateTargetContainer: nil, addStandaloneReactionAnimation: "".isEmpty ? nil : { [weak self] standaloneReactionAnimation in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
standaloneReactionAnimation.frame = self.bounds
|
||||
self.addSubview(standaloneReactionAnimation.view)
|
||||
}, completion: { [weak targetView, weak reactionContextNode] in
|
||||
targetView?.removeFromSuperview()
|
||||
if let reactionContextNode {
|
||||
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
|
||||
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
||||
reactionContextNode?.view.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if hasFirstResponder(self) {
|
||||
self.sendMessageContext.currentInputMode = .text
|
||||
|
||||
@ -466,6 +466,10 @@ public final class StoryPeerListComponent: Component {
|
||||
return (self.collapsedButton, self.collapsedButton.bounds)
|
||||
}
|
||||
|
||||
public func titleFrame() -> CGRect {
|
||||
return self.titleView.frame
|
||||
}
|
||||
|
||||
public func transitionViewForItem(peerId: EnginePeer.Id) -> (UIView, StoryContainerScreen.TransitionView)? {
|
||||
if self.collapsedButton.isUserInteractionEnabled {
|
||||
return nil
|
||||
|
||||
@ -345,7 +345,7 @@ public final class StorySetIndicatorComponent: Component {
|
||||
if component.hasUnseen {
|
||||
borderColors = [component.theme.chatList.storyUnseenColors.topColor.argb, component.theme.chatList.storyUnseenColors.bottomColor.argb]
|
||||
} else {
|
||||
borderColors = [component.theme.chatList.storySeenColors.topColor.argb, component.theme.chatList.storySeenColors.bottomColor.argb]
|
||||
borderColors = [UIColor(white: 1.0, alpha: 0.3).argb, UIColor(white: 1.0, alpha: 0.3).argb]
|
||||
}
|
||||
|
||||
let imageSize = CGSize(width: maxItemsWidth, height: outerDiameter)
|
||||
|
||||
@ -254,6 +254,10 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
func targetForStoryTransition(id: StoryId) -> UIView? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStatusNode() -> ASDisplayNode? {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -4589,6 +4589,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
guard let item = self.item else {
|
||||
return nil
|
||||
}
|
||||
for contentNode in self.contentNodes {
|
||||
if let value = contentNode.targetForStoryTransition(id: id) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyStoryAttribute {
|
||||
if attribute.storyId == id {
|
||||
|
||||
@ -429,6 +429,10 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
override func targetForStoryTransition(id: StoryId) -> UIView? {
|
||||
return self.interactiveVideoNode.targetForStoryTransition(id: id)
|
||||
}
|
||||
|
||||
override var disablesClipping: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -431,6 +431,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
}
|
||||
|
||||
var replyMessage: Message?
|
||||
var replyStory: StoryId?
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||
var inlineBotNameString: String?
|
||||
@ -467,28 +469,34 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
}
|
||||
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
|
||||
} else {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
story: nil,
|
||||
parentMessage: item.message,
|
||||
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
|
||||
associatedData: item.associatedData
|
||||
))
|
||||
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
|
||||
}
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyStory = attribute.storyId
|
||||
} else if let _ = attribute as? InlineBotMessageAttribute {
|
||||
} else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty {
|
||||
replyMarkup = attribute
|
||||
}
|
||||
}
|
||||
|
||||
if replyMessage != nil || replyStory != nil {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
story: replyStory,
|
||||
parentMessage: item.message,
|
||||
constrainedSize: CGSize(width: max(0, availableWidth), height: CGFloat.greatestFiniteMagnitude),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
|
||||
associatedData: item.associatedData
|
||||
))
|
||||
}
|
||||
|
||||
var updatedShareButtonNode: ChatMessageShareButton?
|
||||
if needsShareButton {
|
||||
|
||||
@ -316,6 +316,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
let availableContentWidth = width - bubbleEdgeInset * 2.0 - bubbleContentInsetsLeft - 20.0
|
||||
|
||||
if !ignoreHeaders {
|
||||
var replyMessage: Message?
|
||||
var replyStory: StoryId?
|
||||
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||
var inlineBotNameString: String?
|
||||
@ -338,23 +341,32 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
|
||||
} else {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
story: nil,
|
||||
parentMessage: item.message,
|
||||
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
|
||||
associatedData: item.associatedData
|
||||
))
|
||||
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
|
||||
}
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyStory = attribute.storyId
|
||||
}
|
||||
}
|
||||
|
||||
if replyMessage != nil || replyStory != nil {
|
||||
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyMessage?.id {
|
||||
} else {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
story: replyStory,
|
||||
parentMessage: item.message,
|
||||
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
|
||||
associatedData: item.associatedData
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1259,6 +1271,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
return
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1815,4 +1830,20 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
|
||||
self.canAttachContent = false
|
||||
}
|
||||
|
||||
func targetForStoryTransition(id: StoryId) -> UIView? {
|
||||
guard let item = self.item else {
|
||||
return nil
|
||||
}
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyStoryAttribute {
|
||||
if attribute.storyId == id {
|
||||
if let replyInfoNode = self.replyInfoNode {
|
||||
return replyInfoNode.mediaTransitionView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +188,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
case let .bubble(incoming):
|
||||
titleColor = incoming ? (authorNameColor ?? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor) : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
lineImage = incoming ? (authorNameColor.flatMap({ PresentationResourcesChat.chatBubbleVerticalLineImage(color: $0) }) ?? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(arguments.presentationData.theme.theme)) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(arguments.presentationData.theme.theme)
|
||||
if isMedia || isExpiredStory {
|
||||
if isExpiredStory {
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
} else if isMedia {
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||
} else {
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
||||
|
||||
@ -52,7 +52,7 @@ public func chatInputStateStringWithAppliedEntities(_ text: String, entities: [M
|
||||
return string
|
||||
}
|
||||
|
||||
public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, blockQuoteFont: UIFont, underlineLinks: Bool = true, external: Bool = false, message: Message?) -> NSAttributedString {
|
||||
public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, blockQuoteFont: UIFont, underlineLinks: Bool = true, external: Bool = false, message: Message?, entityFiles: [MediaId: TelegramMediaFile] = [:]) -> NSAttributedString {
|
||||
var nsString: NSString?
|
||||
let string = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.font: baseFont, NSAttributedString.Key.foregroundColor: baseColor])
|
||||
var skipEntity = false
|
||||
@ -252,7 +252,14 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
||||
}
|
||||
}
|
||||
case let .CustomEmoji(_, fileId):
|
||||
string.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message?.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile), range: range)
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
var emojiFile: TelegramMediaFile?
|
||||
if let file = message?.associatedMedia[mediaId] as? TelegramMediaFile {
|
||||
emojiFile = file
|
||||
} else {
|
||||
emojiFile = entityFiles[mediaId]
|
||||
}
|
||||
string.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: emojiFile), range: range)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user