mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Restore changes
This commit is contained in:
parent
e0b11ea319
commit
624d0d49d5
@ -1243,7 +1243,7 @@ public final class ChatListNode: ListView {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.useMainQueueTransactions = true
|
//self.useMainQueueTransactions = true
|
||||||
|
|
||||||
self.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor
|
self.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor
|
||||||
self.verticalScrollIndicatorFollowsOverscroll = true
|
self.verticalScrollIndicatorFollowsOverscroll = true
|
||||||
|
@ -840,8 +840,8 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
|||||||
self.controlsContainerNode.addSubnode(self.stripContainerNode)
|
self.controlsContainerNode.addSubnode(self.stripContainerNode)
|
||||||
self.controlsClippingNode.addSubnode(self.controlsContainerNode)
|
self.controlsClippingNode.addSubnode(self.controlsContainerNode)
|
||||||
self.controlsClippingOffsetNode.addSubnode(self.controlsClippingNode)
|
self.controlsClippingOffsetNode.addSubnode(self.controlsClippingNode)
|
||||||
self.stripContainerNode.addSubnode(self.setByYouNode)
|
self.addSubnode(self.setByYouNode)
|
||||||
self.stripContainerNode.addSubnode(self.setByYouImageNode)
|
self.addSubnode(self.setByYouImageNode)
|
||||||
|
|
||||||
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
|
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -1435,7 +1435,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
|||||||
transition.updateAlpha(node: self.setByYouNode, alpha: 0.7)
|
transition.updateAlpha(node: self.setByYouNode, alpha: 0.7)
|
||||||
self.setByYouNode.attributedText = NSAttributedString(string: photoTitle, font: Font.regular(12.0), textColor: UIColor.white)
|
self.setByYouNode.attributedText = NSAttributedString(string: photoTitle, font: Font.regular(12.0), textColor: UIColor.white)
|
||||||
let setByYouSize = self.setByYouNode.updateLayout(size)
|
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
|
self.setByYouNode.isUserInteractionEnabled = hasLink
|
||||||
} else {
|
} else {
|
||||||
transition.updateAlpha(node: self.setByYouNode, alpha: 0.0)
|
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 isReactionSearchActive: Bool = false
|
||||||
|
|
||||||
|
public var reduceMotion: Bool = false
|
||||||
|
|
||||||
public static func randomGenericReactionEffect(context: AccountContext) -> Signal<String?, NoError> {
|
public static func randomGenericReactionEffect(context: AccountContext) -> Signal<String?, NoError> {
|
||||||
return context.engine.stickers.loadedStickerPack(reference: .emojiGenericAnimations, forceActualized: false)
|
return context.engine.stickers.loadedStickerPack(reference: .emojiGenericAnimations, forceActualized: false)
|
||||||
|> map { result -> [TelegramMediaFile]? in
|
|> map { result -> [TelegramMediaFile]? in
|
||||||
@ -907,7 +909,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if animateIn {
|
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 {
|
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 springDuration: Double = 0.5
|
||||||
let springDamping: CGFloat = 104.0
|
let springDamping: CGFloat = 104.0
|
||||||
let springScaleDelay: Double = 0.1
|
let springScaleDelay: Double = 0.1
|
||||||
@ -1606,11 +1608,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
let mainCircleDelay: Double = 0.01
|
let mainCircleDelay: Double = 0.01
|
||||||
|
|
||||||
self.backgroundNode.animateIn()
|
if !self.presentationData.reduceMotion && !self.reduceMotion {
|
||||||
|
self.backgroundNode.animateIn()
|
||||||
|
}
|
||||||
|
|
||||||
self.didAnimateIn = true
|
self.didAnimateIn = true
|
||||||
|
|
||||||
if !self.presentationData.reduceMotion {
|
if !self.presentationData.reduceMotion && !self.reduceMotion {
|
||||||
for i in 0 ..< self.items.count {
|
for i in 0 ..< self.items.count {
|
||||||
guard let itemNode = self.visibleItemNodes[i] else {
|
guard let itemNode = self.visibleItemNodes[i] else {
|
||||||
continue
|
continue
|
||||||
|
@ -466,17 +466,18 @@ public final class PeerStoryListContext {
|
|||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.isArchived = isArchived
|
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)
|
let key = ValueBoxKey(length: 8 + 1)
|
||||||
key.setInt64(0, value: peerId.toInt64())
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
key.setInt8(8, value: isArchived ? 1 : 0)
|
key.setInt8(8, value: isArchived ? 1 : 0)
|
||||||
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
|
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
|
||||||
guard let cached = cached else {
|
guard let cached = cached else {
|
||||||
return (nil, [], 0)
|
return (nil, [], 0, [:])
|
||||||
}
|
}
|
||||||
var items: [EngineStoryItem] = []
|
var items: [EngineStoryItem] = []
|
||||||
|
var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||||
for storedItem in cached.items {
|
for storedItem in cached.items {
|
||||||
if case let .item(item) = storedItem, let media = item.media {
|
if case let .item(item) = storedItem, let media = item.media {
|
||||||
let mappedItem = EngineStoryItem(
|
let mappedItem = EngineStoryItem(
|
||||||
@ -504,19 +505,30 @@ public final class PeerStoryListContext {
|
|||||||
isEdited: item.isEdited
|
isEdited: item.isEdited
|
||||||
)
|
)
|
||||||
items.append(mappedItem)
|
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)
|
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 {
|
guard let `self` = self else {
|
||||||
return
|
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()
|
self.loadMore()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -828,19 +840,22 @@ public final class PeerStoryListContext {
|
|||||||
public var totalCount: Int
|
public var totalCount: Int
|
||||||
public var loadMoreToken: Int?
|
public var loadMoreToken: Int?
|
||||||
public var isCached: Bool
|
public var isCached: Bool
|
||||||
|
public var allEntityFiles: [MediaId: TelegramMediaFile]
|
||||||
|
|
||||||
init(
|
init(
|
||||||
peerReference: PeerReference?,
|
peerReference: PeerReference?,
|
||||||
items: [EngineStoryItem],
|
items: [EngineStoryItem],
|
||||||
totalCount: Int,
|
totalCount: Int,
|
||||||
loadMoreToken: Int?,
|
loadMoreToken: Int?,
|
||||||
isCached: Bool
|
isCached: Bool,
|
||||||
|
allEntityFiles: [MediaId: TelegramMediaFile]
|
||||||
) {
|
) {
|
||||||
self.peerReference = peerReference
|
self.peerReference = peerReference
|
||||||
self.items = items
|
self.items = items
|
||||||
self.totalCount = totalCount
|
self.totalCount = totalCount
|
||||||
self.loadMoreToken = loadMoreToken
|
self.loadMoreToken = loadMoreToken
|
||||||
self.isCached = isCached
|
self.isCached = isCached
|
||||||
|
self.allEntityFiles = allEntityFiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ public final class ChatListNavigationBar: Component {
|
|||||||
|
|
||||||
public static let searchScrollHeight: CGFloat = 52.0
|
public static let searchScrollHeight: CGFloat = 52.0
|
||||||
public static let storiesScrollHeight: CGFloat = {
|
public static let storiesScrollHeight: CGFloat = {
|
||||||
return 79.0
|
return 83.0
|
||||||
}()
|
}()
|
||||||
|
|
||||||
public final class View: UIView {
|
public final class View: UIView {
|
||||||
@ -404,7 +404,7 @@ public final class ChatListNavigationBar: Component {
|
|||||||
if component.statusBarHeight < 1.0 {
|
if component.statusBarHeight < 1.0 {
|
||||||
headerContentY = 0.0
|
headerContentY = 0.0
|
||||||
} else {
|
} else {
|
||||||
headerContentY = component.statusBarHeight + 12.0
|
headerContentY = component.statusBarHeight + 5.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let headerContentFrame = CGRect(origin: CGPoint(x: 0.0, y: headerContentY), size: headerContentSize)
|
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
|
var contentHeight = component.statusBarHeight
|
||||||
|
|
||||||
if component.statusBarHeight >= 1.0 {
|
if component.statusBarHeight >= 1.0 {
|
||||||
contentHeight += 10.0
|
contentHeight += 3.0
|
||||||
}
|
}
|
||||||
contentHeight += 44.0
|
contentHeight += 44.0
|
||||||
|
|
||||||
|
@ -63,9 +63,11 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||||||
),
|
),
|
||||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global())
|
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global())
|
||||||
)
|
)
|
||||||
|> mapToSignal { _, views, globalNotificationSettings -> Signal<(CombinedView, [PeerId: Peer], EngineGlobalNotificationSettings), NoError> in
|
|> mapToSignal { _, views, globalNotificationSettings -> Signal<(CombinedView, [PeerId: Peer], EngineGlobalNotificationSettings, [MediaId: TelegramMediaFile]), NoError> in
|
||||||
return context.account.postbox.transaction { transaction -> (CombinedView, [PeerId: Peer], EngineGlobalNotificationSettings) in
|
return context.account.postbox.transaction { transaction -> (CombinedView, [PeerId: Peer], EngineGlobalNotificationSettings, [MediaId: TelegramMediaFile]) in
|
||||||
var peers: [PeerId: Peer] = [:]
|
var peers: [PeerId: Peer] = [:]
|
||||||
|
var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||||
|
|
||||||
if let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView {
|
if let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView {
|
||||||
for item in itemsView.items {
|
for item in itemsView.items {
|
||||||
if let item = item.value.get(Stories.StoredItem.self), case let .item(itemValue) = item {
|
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 {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -257,7 +270,8 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||||||
return StoryContentItem(
|
return StoryContentItem(
|
||||||
position: nil,
|
position: nil,
|
||||||
peerId: peer.id,
|
peerId: peer.id,
|
||||||
storyItem: item
|
storyItem: item,
|
||||||
|
entityFiles: extractItemEntityFiles(item: item, allEntityFiles: allEntityFiles)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +282,8 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||||||
item: StoryContentItem(
|
item: StoryContentItem(
|
||||||
position: mappedFocusedIndex ?? focusedIndex,
|
position: mappedFocusedIndex ?? focusedIndex,
|
||||||
peerId: peer.id,
|
peerId: peer.id,
|
||||||
storyItem: mappedItem
|
storyItem: mappedItem,
|
||||||
|
entityFiles: extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
|
||||||
),
|
),
|
||||||
totalCount: totalCount,
|
totalCount: totalCount,
|
||||||
previousItemId: previousItemId,
|
previousItemId: previousItemId,
|
||||||
@ -907,11 +922,12 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
|||||||
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId),
|
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId),
|
||||||
TelegramEngine.EngineData.Item.NotificationSettings.Global()
|
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 {
|
guard let item = transaction.getStory(id: storyId)?.get(Stories.StoredItem.self) else {
|
||||||
return (nil, [:])
|
return (nil, [:], [:])
|
||||||
}
|
}
|
||||||
var peers: [PeerId: Peer] = [:]
|
var peers: [PeerId: Peer] = [:]
|
||||||
|
var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||||
if case let .item(item) = item {
|
if case let .item(item) = item {
|
||||||
if let views = item.views {
|
if let views = item.views {
|
||||||
for id in views.seenPeerIds {
|
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
|
|> deliverOnMainQueue).start(next: { [weak self] data, itemAndPeers in
|
||||||
@ -930,7 +956,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
|
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
|
||||||
let (item, peers) = itemAndPeers
|
let (item, peers, allEntityFiles) = itemAndPeers
|
||||||
|
|
||||||
guard let peer else {
|
guard let peer else {
|
||||||
return
|
return
|
||||||
@ -981,7 +1007,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
|||||||
let mainItem = StoryContentItem(
|
let mainItem = StoryContentItem(
|
||||||
position: 0,
|
position: 0,
|
||||||
peerId: peer.id,
|
peerId: peer.id,
|
||||||
storyItem: mappedItem
|
storyItem: mappedItem,
|
||||||
|
entityFiles: extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
|
||||||
)
|
)
|
||||||
let stateValue = StoryContentContextState(
|
let stateValue = StoryContentContextState(
|
||||||
slice: StoryContentContextState.FocusedSlice(
|
slice: StoryContentContextState.FocusedSlice(
|
||||||
@ -1130,7 +1157,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
|||||||
return StoryContentItem(
|
return StoryContentItem(
|
||||||
position: nil,
|
position: nil,
|
||||||
peerId: peer.id,
|
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(
|
item: StoryContentItem(
|
||||||
position: nil,
|
position: nil,
|
||||||
peerId: peer.id,
|
peerId: peer.id,
|
||||||
storyItem: item
|
storyItem: item,
|
||||||
|
entityFiles: extractItemEntityFiles(item: item, allEntityFiles: state.allEntityFiles)
|
||||||
),
|
),
|
||||||
totalCount: state.totalCount,
|
totalCount: state.totalCount,
|
||||||
previousItemId: focusedIndex == 0 ? nil : state.items[focusedIndex - 1].id,
|
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
|
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
|
||||||
|
}
|
||||||
|
@ -80,15 +80,18 @@ public final class StoryContentItem: Equatable {
|
|||||||
public let position: Int?
|
public let position: Int?
|
||||||
public let peerId: EnginePeer.Id?
|
public let peerId: EnginePeer.Id?
|
||||||
public let storyItem: EngineStoryItem
|
public let storyItem: EngineStoryItem
|
||||||
|
public let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
position: Int?,
|
position: Int?,
|
||||||
peerId: EnginePeer.Id?,
|
peerId: EnginePeer.Id?,
|
||||||
storyItem: EngineStoryItem
|
storyItem: EngineStoryItem,
|
||||||
|
entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||||
) {
|
) {
|
||||||
self.position = position
|
self.position = position
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.storyItem = storyItem
|
self.storyItem = storyItem
|
||||||
|
self.entityFiles = entityFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: StoryContentItem, rhs: StoryContentItem) -> Bool {
|
public static func ==(lhs: StoryContentItem, rhs: StoryContentItem) -> Bool {
|
||||||
@ -101,6 +104,9 @@ public final class StoryContentItem: Equatable {
|
|||||||
if lhs.storyItem != rhs.storyItem {
|
if lhs.storyItem != rhs.storyItem {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.entityFiles != rhs.entityFiles {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let text: String
|
let text: String
|
||||||
let entities: [MessageTextEntity]
|
let entities: [MessageTextEntity]
|
||||||
|
let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||||
let action: (Action) -> Void
|
let action: (Action) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -50,12 +51,14 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
text: String,
|
text: String,
|
||||||
entities: [MessageTextEntity],
|
entities: [MessageTextEntity],
|
||||||
|
entityFiles: [EngineMedia.Id: TelegramMediaFile],
|
||||||
action: @escaping (Action) -> Void
|
action: @escaping (Action) -> Void
|
||||||
) {
|
) {
|
||||||
self.externalState = externalState
|
self.externalState = externalState
|
||||||
self.context = context
|
self.context = context
|
||||||
self.text = text
|
self.text = text
|
||||||
self.entities = entities
|
self.entities = entities
|
||||||
|
self.entityFiles = entityFiles
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +75,9 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
if lhs.entities != rhs.entities {
|
if lhs.entities != rhs.entities {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.entityFiles != rhs.entityFiles {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +365,8 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
boldItalicFont: Font.semiboldItalic(16.0),
|
boldItalicFont: Font.semiboldItalic(16.0),
|
||||||
fixedFont: Font.monospace(16.0),
|
fixedFont: Font.monospace(16.0),
|
||||||
blockQuoteFont: Font.monospace(16.0),
|
blockQuoteFont: Font.monospace(16.0),
|
||||||
message: nil
|
message: nil,
|
||||||
|
entityFiles: component.entityFiles
|
||||||
)
|
)
|
||||||
|
|
||||||
let makeLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
let makeLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||||
|
@ -2518,6 +2518,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
context: component.context,
|
context: component.context,
|
||||||
text: component.slice.item.storyItem.text,
|
text: component.slice.item.storyItem.text,
|
||||||
entities: component.slice.item.storyItem.entities,
|
entities: component.slice.item.storyItem.entities,
|
||||||
|
entityFiles: component.slice.item.entityFiles,
|
||||||
action: { [weak self] action in
|
action: { [weak self] action in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
@ -2649,7 +2650,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
reactionContextNode.displayTail = false
|
reactionContextNode.displayTail = false
|
||||||
self.reactionContextNode = reactionContextNode
|
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 {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2673,23 +2674,24 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
targetView.isUserInteractionEnabled = false
|
targetView.isUserInteractionEnabled = false
|
||||||
self.addSubview(targetView)
|
self.addSubview(targetView)
|
||||||
|
|
||||||
reactionContextNode.willAnimateOutToReaction(value: updateReaction.reaction)
|
if let reactionContextNode {
|
||||||
reactionContextNode.animateOutToReaction(value: updateReaction.reaction, targetView: targetView, hideNode: false, animateTargetContainer: nil, addStandaloneReactionAnimation: "".isEmpty ? nil : { [weak self] standaloneReactionAnimation in
|
reactionContextNode.willAnimateOutToReaction(value: updateReaction.reaction)
|
||||||
guard let self else {
|
reactionContextNode.animateOutToReaction(value: updateReaction.reaction, targetView: targetView, hideNode: false, animateTargetContainer: nil, addStandaloneReactionAnimation: "".isEmpty ? nil : { [weak self] standaloneReactionAnimation in
|
||||||
return
|
guard let self else {
|
||||||
}
|
return
|
||||||
standaloneReactionAnimation.frame = self.bounds
|
}
|
||||||
self.addSubview(standaloneReactionAnimation.view)
|
standaloneReactionAnimation.frame = self.bounds
|
||||||
}, completion: { [weak targetView, weak reactionContextNode] in
|
self.addSubview(standaloneReactionAnimation.view)
|
||||||
targetView?.removeFromSuperview()
|
}, completion: { [weak targetView, weak reactionContextNode] in
|
||||||
if let reactionContextNode {
|
targetView?.removeFromSuperview()
|
||||||
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
|
if let reactionContextNode {
|
||||||
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
|
||||||
reactionContextNode?.view.removeFromSuperview()
|
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
||||||
})
|
reactionContextNode?.view.removeFromSuperview()
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if hasFirstResponder(self) {
|
if hasFirstResponder(self) {
|
||||||
self.sendMessageContext.currentInputMode = .text
|
self.sendMessageContext.currentInputMode = .text
|
||||||
|
@ -345,7 +345,7 @@ public final class StorySetIndicatorComponent: Component {
|
|||||||
if component.hasUnseen {
|
if component.hasUnseen {
|
||||||
borderColors = [component.theme.chatList.storyUnseenColors.topColor.argb, component.theme.chatList.storyUnseenColors.bottomColor.argb]
|
borderColors = [component.theme.chatList.storyUnseenColors.topColor.argb, component.theme.chatList.storyUnseenColors.bottomColor.argb]
|
||||||
} else {
|
} 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)
|
let imageSize = CGSize(width: maxItemsWidth, height: outerDiameter)
|
||||||
|
@ -254,6 +254,10 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func targetForStoryTransition(id: StoryId) -> UIView? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getStatusNode() -> ASDisplayNode? {
|
func getStatusNode() -> ASDisplayNode? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4589,6 +4589,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
for contentNode in self.contentNodes {
|
||||||
|
if let value = contentNode.targetForStoryTransition(id: id) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
if let attribute = attribute as? ReplyStoryAttribute {
|
if let attribute = attribute as? ReplyStoryAttribute {
|
||||||
if attribute.storyId == id {
|
if attribute.storyId == id {
|
||||||
|
@ -429,6 +429,10 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func targetForStoryTransition(id: StoryId) -> UIView? {
|
||||||
|
return self.interactiveVideoNode.targetForStoryTransition(id: id)
|
||||||
|
}
|
||||||
|
|
||||||
override var disablesClipping: Bool {
|
override var disablesClipping: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -431,6 +431,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var replyMessage: Message?
|
||||||
|
var replyStory: StoryId?
|
||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||||
var inlineBotNameString: String?
|
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 {
|
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
|
||||||
} else {
|
} else {
|
||||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
|
||||||
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
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||||
|
replyStory = attribute.storyId
|
||||||
} else if let _ = attribute as? InlineBotMessageAttribute {
|
} else if let _ = attribute as? InlineBotMessageAttribute {
|
||||||
} else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty {
|
} else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty {
|
||||||
replyMarkup = attribute
|
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?
|
var updatedShareButtonNode: ChatMessageShareButton?
|
||||||
if needsShareButton {
|
if needsShareButton {
|
||||||
|
@ -316,6 +316,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
let availableContentWidth = width - bubbleEdgeInset * 2.0 - bubbleContentInsetsLeft - 20.0
|
let availableContentWidth = width - bubbleEdgeInset * 2.0 - bubbleContentInsetsLeft - 20.0
|
||||||
|
|
||||||
if !ignoreHeaders {
|
if !ignoreHeaders {
|
||||||
|
var replyMessage: Message?
|
||||||
|
var replyStory: StoryId?
|
||||||
|
|
||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||||
var inlineBotNameString: String?
|
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 {
|
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
|
||||||
} else {
|
} else {
|
||||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
|
||||||
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
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
} 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 {
|
if let attribute = attribute as? ReplyMessageAttribute {
|
||||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||||
return
|
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
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ public func chatInputStateStringWithAppliedEntities(_ text: String, entities: [M
|
|||||||
return string
|
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?
|
var nsString: NSString?
|
||||||
let string = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.font: baseFont, NSAttributedString.Key.foregroundColor: baseColor])
|
let string = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.font: baseFont, NSAttributedString.Key.foregroundColor: baseColor])
|
||||||
var skipEntity = false
|
var skipEntity = false
|
||||||
@ -252,7 +252,14 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .CustomEmoji(_, fileId):
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user