This commit is contained in:
Ali 2023-07-12 20:18:01 +04:00
parent 860124e3be
commit f1a01e343b
32 changed files with 526 additions and 110 deletions

View File

@ -1881,24 +1881,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.chatListDisplayNode.scrollToTopIfStoriesAreExpanded() self.chatListDisplayNode.scrollToTopIfStoriesAreExpanded()
} }
/*self.chatListDisplayNode.mainContainerNode.currentItemNode.updateState { chatListState in
var chatListState = chatListState
var peerStoryMapping: [EnginePeer.Id: ChatListNodeState.StoryState] = [:]
for item in rawStorySubscriptions.items {
if item.peer.id == self.context.account.peerId {
continue
}
peerStoryMapping[item.peer.id] = ChatListNodeState.StoryState(
hasUnseen: item.hasUnseen,
hasUnseenCloseFriends: item.hasUnseenCloseFriends
)
}
chatListState.peerStoryMapping = peerStoryMapping
return chatListState
}*/
self.storiesReady.set(.single(true)) self.storiesReady.set(.single(true))
Queue.mainQueue().after(1.0, { [weak self] in Queue.mainQueue().after(1.0, { [weak self] in
@ -1935,12 +1917,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
unseenCount += 1 unseenCount += 1
} }
} }
let hasUnseenCloseFriends = rawStoryArchiveSubscriptions.items.contains(where: { $0.hasUnseenCloseFriends })
archiveStoryState = ChatListNodeState.StoryState( archiveStoryState = ChatListNodeState.StoryState(
stats: EngineChatList.StoryStats( stats: EngineChatList.StoryStats(
totalCount: rawStoryArchiveSubscriptions.items.count, totalCount: rawStoryArchiveSubscriptions.items.count,
unseenCount: unseenCount unseenCount: unseenCount,
hasUnseenCloseFriends: hasUnseenCloseFriends
), ),
hasUnseenCloseFriends: rawStoryArchiveSubscriptions.items.contains(where: { $0.hasUnseenCloseFriends }) hasUnseenCloseFriends: hasUnseenCloseFriends
) )
} }
@ -2823,13 +2807,28 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
guard let self else { guard let self else {
return return
} }
let undoValue: Bool
if self.location == .chatList(groupId: .archive) { if self.location == .chatList(groupId: .archive) {
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false) self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false)
undoValue = true
} else { } else {
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true) self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true)
undoValue = false
} }
guard let parentController = self.parent as? TabBarController, let contactsController = (self.navigationController as? TelegramRootControllerInterface)?.getContactsController(), let sourceFrame = parentController.frameForControllerTab(controller: contactsController) else { //TODO:localize
if self.location != .chatList(groupId: .archive) {
self.present(UndoOverlayController(presentationData: self.presentationData, content: .archivedChat(peerId: peer.id.toInt64(), title: "", text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Archived Chats.", undo: true), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in
if case .undo = action {
if let self {
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: undoValue)
}
}
return false
}), in: .current)
}
/*guard let parentController = self.parent as? TabBarController, let contactsController = (self.navigationController as? TelegramRootControllerInterface)?.getContactsController(), let sourceFrame = parentController.frameForControllerTab(controller: contactsController) else {
return return
} }
@ -2851,7 +2850,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
location: .point(location, .bottom), location: .point(location, .bottom),
shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) } shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) }
) )
self.present(tooltipController, in: .window(.root)) self.present(tooltipController, in: .window(.root))*/
}))) })))
} }

View File

@ -2107,6 +2107,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if let action = media as? TelegramMediaAction, case let .suggestedProfilePhoto(image) = action.action, let _ = image { } else if let action = media as? TelegramMediaAction, case let .suggestedProfilePhoto(image) = action.action, let _ = image {
let fitSize = contentImageSize let fitSize = contentImageSize
contentImageSpecs.append((message, .action(action), fitSize)) contentImageSpecs.append((message, .action(action), fitSize))
} else if let storyMedia = media as? TelegramMediaStory, let story = message.associatedStories[storyMedia.storyId], !story.data.isEmpty, case let .item(storyItem) = story.get(Stories.StoredItem.self) {
if let image = storyItem.media as? TelegramMediaImage {
if let _ = largestImageRepresentation(image.representations) {
let fitSize = contentImageSize
contentImageSpecs.append((message, .image(image), fitSize))
}
break inner
} else if let file = storyItem.media as? TelegramMediaFile {
if file.isVideo, !file.isInstantVideo, let _ = file.dimensions {
let fitSize = contentImageSize
contentImageSpecs.append((message, .file(file), fitSize))
}
break inner
}
} }
} }
} }
@ -3369,7 +3383,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
inputActivitiesApply?() inputActivitiesApply?()
var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: floor((measureLayout.size.height - contentImageSize.height) / 2.0)) var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: 1.0 + floor((measureLayout.size.height - contentImageSize.height) / 2.0))
var messageTypeIcon: UIImage? var messageTypeIcon: UIImage?
var messageTypeIconOffset = mediaPreviewOffset var messageTypeIconOffset = mediaPreviewOffset

View File

@ -697,7 +697,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
storyState: entry.renderedPeer.peerId == accountPeerId ? nil : entry.storyStats.flatMap { stats -> ChatListNodeState.StoryState in storyState: entry.renderedPeer.peerId == accountPeerId ? nil : entry.storyStats.flatMap { stats -> ChatListNodeState.StoryState in
return ChatListNodeState.StoryState( return ChatListNodeState.StoryState(
stats: stats, stats: stats,
hasUnseenCloseFriends: false hasUnseenCloseFriends: stats.hasUnseenCloseFriends
) )
} }
)) ))

View File

@ -1187,7 +1187,7 @@ open class TextNode: ASDisplayNode {
if brokenLineRange.location + brokenLineRange.length > attributedString.length { if brokenLineRange.location + brokenLineRange.length > attributedString.length {
brokenLineRange.length = attributedString.length - brokenLineRange.location brokenLineRange.length = attributedString.length - brokenLineRange.location
} }
if lineRange.length == 0 { if lineRange.length == 0 && !didClipLinebreak {
break break
} }
@ -1202,7 +1202,11 @@ open class TextNode: ASDisplayNode {
let truncatedTokenString: NSAttributedString let truncatedTokenString: NSAttributedString
if let customTruncationToken { if let customTruncationToken {
truncatedTokenString = customTruncationToken if lineRange.length == 0 && customTruncationToken.string.hasPrefix("\u{2026} ") {
truncatedTokenString = customTruncationToken.attributedSubstring(from: NSRange(location: 2, length: customTruncationToken.length - 2))
} else {
truncatedTokenString = customTruncationToken
}
} else { } else {
var truncationTokenAttributes: [NSAttributedString.Key : AnyObject] = [:] var truncationTokenAttributes: [NSAttributedString.Key : AnyObject] = [:]
truncationTokenAttributes[NSAttributedString.Key.font] = font truncationTokenAttributes[NSAttributedString.Key.font] = font

View File

@ -1358,7 +1358,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: storyStats.totalCount, totalCount: storyStats.totalCount,
unseenCount: storyStats.unseenCount, unseenCount: storyStats.unseenCount,
hasUnseenCloseFriendsItems: false hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(theme: item.presentationData.theme), colors: AvatarNode.Colors(theme: item.presentationData.theme),

View File

@ -260,10 +260,12 @@ public enum ChatListEntry: Comparable {
public struct PeerStoryStats: Equatable { public struct PeerStoryStats: Equatable {
public var totalCount: Int public var totalCount: Int
public var unseenCount: Int public var unseenCount: Int
public var hasUnseenCloseFriends: Bool
public init(totalCount: Int, unseenCount: Int) { public init(totalCount: Int, unseenCount: Int, hasUnseenCloseFriends: Bool) {
self.totalCount = totalCount self.totalCount = totalCount
self.unseenCount = unseenCount self.unseenCount = unseenCount
self.hasUnseenCloseFriends = hasUnseenCloseFriends
} }
} }
@ -282,9 +284,9 @@ func fetchPeerStoryStats(postbox: PostboxImpl, peerId: PeerId) -> PeerStoryStats
if topItems.isExact { if topItems.isExact {
let stats = postbox.storyItemsTable.getStats(peerId: peerId, maxSeenId: maxSeenId) let stats = postbox.storyItemsTable.getStats(peerId: peerId, maxSeenId: maxSeenId)
return PeerStoryStats(totalCount: stats.total, unseenCount: stats.unseen) return PeerStoryStats(totalCount: stats.total, unseenCount: stats.unseen, hasUnseenCloseFriends: stats.hasUnseenCloseFriends)
} else { } else {
return PeerStoryStats(totalCount: 1, unseenCount: topItems.id > maxSeenId ? 1 : 0) return PeerStoryStats(totalCount: 1, unseenCount: topItems.id > maxSeenId ? 1 : 0, hasUnseenCloseFriends: false)
} }
} }

View File

@ -1311,6 +1311,11 @@ public final class Transaction {
self.postbox!.setStoryItemsInexactMaxId(peerId: peerId, id: id) self.postbox!.setStoryItemsInexactMaxId(peerId: peerId, id: id)
} }
public func clearStoryItemsInexactMaxId(peerId: PeerId) {
assert(!self.disposed)
self.postbox!.clearStoryItemsInexactMaxId(peerId: peerId)
}
public func getStoryItems(peerId: PeerId) -> [StoryItemsTableEntry] { public func getStoryItems(peerId: PeerId) -> [StoryItemsTableEntry] {
return self.postbox!.getStoryItems(peerId: peerId) return self.postbox!.getStoryItems(peerId: peerId)
} }
@ -2259,6 +2264,12 @@ final class PostboxImpl {
} }
} }
fileprivate func clearStoryItemsInexactMaxId(peerId: PeerId) {
if let value = self.storyTopItemsTable.get(peerId: peerId), !value.isExact {
self.storyTopItemsTable.set(peerId: peerId, entry: nil, events: &self.currentStoryTopItemEvents)
}
}
fileprivate func getStoryItems(peerId: PeerId) -> [StoryItemsTableEntry] { fileprivate func getStoryItems(peerId: PeerId) -> [StoryItemsTableEntry] {
return self.storyItemsTable.get(peerId: peerId) return self.storyItemsTable.get(peerId: peerId)
} }

View File

@ -4,15 +4,18 @@ public final class StoryItemsTableEntry: Equatable {
public let value: CodableEntry public let value: CodableEntry
public let id: Int32 public let id: Int32
public let expirationTimestamp: Int32? public let expirationTimestamp: Int32?
public let isCloseFriends: Bool
public init( public init(
value: CodableEntry, value: CodableEntry,
id: Int32, id: Int32,
expirationTimestamp: Int32? expirationTimestamp: Int32?,
isCloseFriends: Bool
) { ) {
self.value = value self.value = value
self.id = id self.id = id
self.expirationTimestamp = expirationTimestamp self.expirationTimestamp = expirationTimestamp
self.isCloseFriends = isCloseFriends
} }
public static func ==(lhs: StoryItemsTableEntry, rhs: StoryItemsTableEntry) -> Bool { public static func ==(lhs: StoryItemsTableEntry, rhs: StoryItemsTableEntry) -> Bool {
@ -28,6 +31,9 @@ public final class StoryItemsTableEntry: Equatable {
if lhs.expirationTimestamp != rhs.expirationTimestamp { if lhs.expirationTimestamp != rhs.expirationTimestamp {
return false return false
} }
if lhs.isCloseFriends != rhs.isCloseFriends {
return false
}
return true return true
} }
} }
@ -133,22 +139,53 @@ final class StoryItemsTable: Table {
return key.successor return key.successor
} }
public func getStats(peerId: PeerId, maxSeenId: Int32) -> (total: Int, unseen: Int) { public func getStats(peerId: PeerId, maxSeenId: Int32) -> (total: Int, unseen: Int, hasUnseenCloseFriends: Bool) {
var total = 0 var total = 0
var unseen = 0 var unseen = 0
var hasUnseenCloseFriends = false
self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), keys: { key in self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), values: { key, value in
let id = key.getInt32(8) let id = key.getInt32(8)
total += 1 total += 1
if id > maxSeenId { if id > maxSeenId {
unseen += 1 unseen += 1
var isCloseFriends = false
let readBuffer = ReadBuffer(data: value.makeData())
var magic: UInt32 = 0
readBuffer.read(&magic, offset: 0, length: 4)
if magic == 0xabcd1234 {
} else if magic == 0xabcd1235 {
var length: Int32 = 0
readBuffer.read(&length, offset: 0, length: 4)
if length > 0 && readBuffer.offset + Int(length) <= readBuffer.length {
readBuffer.skip(Int(length))
if readBuffer.offset + 4 <= readBuffer.length {
readBuffer.skip(4)
if readBuffer.offset + 1 <= readBuffer.length {
var flags: UInt8 = 0
readBuffer.read(&flags, offset: 0, length: 1)
isCloseFriends = (flags & (1 << 0)) != 0
}
}
} else {
assertionFailure()
}
} else {
assertionFailure()
}
if isCloseFriends {
hasUnseenCloseFriends = true
}
} }
return true return true
}, limit: 10000) }, limit: 10000)
return (total, unseen) return (total, unseen, hasUnseenCloseFriends)
} }
public func get(peerId: PeerId) -> [StoryItemsTableEntry] { public func get(peerId: PeerId) -> [StoryItemsTableEntry] {
@ -161,6 +198,7 @@ final class StoryItemsTable: Table {
let entry: CodableEntry let entry: CodableEntry
var expirationTimestamp: Int32? var expirationTimestamp: Int32?
var isCloseFriends = false
let readBuffer = ReadBuffer(data: value.makeData()) let readBuffer = ReadBuffer(data: value.makeData())
var magic: UInt32 = 0 var magic: UInt32 = 0
@ -173,16 +211,41 @@ final class StoryItemsTable: Table {
if readBuffer.offset + 4 <= readBuffer.length { if readBuffer.offset + 4 <= readBuffer.length {
var expirationTimestampValue: Int32 = 0 var expirationTimestampValue: Int32 = 0
readBuffer.read(&expirationTimestampValue, offset: 0, length: 4) readBuffer.read(&expirationTimestampValue, offset: 0, length: 4)
expirationTimestamp = expirationTimestampValue if expirationTimestampValue != 0 {
expirationTimestamp = expirationTimestampValue
}
} }
} else { } else {
entry = CodableEntry(data: Data()) entry = CodableEntry(data: Data())
} }
} else if magic == 0xabcd1235 {
var length: Int32 = 0
readBuffer.read(&length, offset: 0, length: 4)
if length > 0 && readBuffer.offset + Int(length) <= readBuffer.length {
entry = CodableEntry(data: readBuffer.readData(length: Int(length)))
if readBuffer.offset + 4 <= readBuffer.length {
var expirationTimestampValue: Int32 = 0
readBuffer.read(&expirationTimestampValue, offset: 0, length: 4)
if expirationTimestampValue != 0 {
expirationTimestamp = expirationTimestampValue
}
if readBuffer.offset + 1 <= readBuffer.length {
var flags: UInt8 = 0
readBuffer.read(&flags, offset: 0, length: 1)
isCloseFriends = (flags & (1 << 0)) != 0
}
}
} else {
assertionFailure()
entry = CodableEntry(data: Data())
}
} else { } else {
assertionFailure()
entry = CodableEntry(data: value.makeData()) entry = CodableEntry(data: value.makeData())
} }
result.append(StoryItemsTableEntry(value: entry, id: id, expirationTimestamp: expirationTimestamp)) result.append(StoryItemsTableEntry(value: entry, id: id, expirationTimestamp: expirationTimestamp, isCloseFriends: isCloseFriends))
return true return true
}, limit: 10000) }, limit: 10000)
@ -209,7 +272,22 @@ final class StoryItemsTable: Table {
if readBuffer.offset + 4 <= readBuffer.length { if readBuffer.offset + 4 <= readBuffer.length {
var expirationTimestampValue: Int32 = 0 var expirationTimestampValue: Int32 = 0
readBuffer.read(&expirationTimestampValue, offset: 0, length: 4) readBuffer.read(&expirationTimestampValue, offset: 0, length: 4)
expirationTimestamp = expirationTimestampValue if expirationTimestampValue != 0 {
expirationTimestamp = expirationTimestampValue
}
}
}
} else if magic == 0xabcd1235 {
var length: Int32 = 0
readBuffer.read(&length, offset: 0, length: 4)
if length > 0 && readBuffer.offset + Int(length) <= readBuffer.length {
readBuffer.skip(Int(length))
if readBuffer.offset + 4 <= readBuffer.length {
var expirationTimestampValue: Int32 = 0
readBuffer.read(&expirationTimestampValue, offset: 0, length: 4)
if expirationTimestampValue != 0 {
expirationTimestamp = expirationTimestampValue
}
} }
} }
} }
@ -244,7 +322,22 @@ final class StoryItemsTable: Table {
if readBuffer.offset + 4 <= readBuffer.length { if readBuffer.offset + 4 <= readBuffer.length {
var expirationTimestampValue: Int32 = 0 var expirationTimestampValue: Int32 = 0
readBuffer.read(&expirationTimestampValue, offset: 0, length: 4) readBuffer.read(&expirationTimestampValue, offset: 0, length: 4)
expirationTimestamp = expirationTimestampValue if expirationTimestampValue != 0 {
expirationTimestamp = expirationTimestampValue
}
}
}
} else if magic == 0xabcd1235 {
var length: Int32 = 0
readBuffer.read(&length, offset: 0, length: 4)
if length > 0 && readBuffer.offset + Int(length) <= readBuffer.length {
readBuffer.skip(Int(length))
if readBuffer.offset + 4 <= readBuffer.length {
var expirationTimestampValue: Int32 = 0
readBuffer.read(&expirationTimestampValue, offset: 0, length: 4)
if expirationTimestampValue != 0 {
expirationTimestamp = expirationTimestampValue
}
} }
} }
} }
@ -279,17 +372,21 @@ final class StoryItemsTable: Table {
for entry in entries { for entry in entries {
buffer.reset() buffer.reset()
var magic: UInt32 = 0xabcd1234 var magic: UInt32 = 0xabcd1235
buffer.write(&magic, length: 4) buffer.write(&magic, length: 4)
var length: Int32 = Int32(entry.value.data.count) var length: Int32 = Int32(entry.value.data.count)
buffer.write(&length, length: 4) buffer.write(&length, length: 4)
buffer.write(entry.value.data) buffer.write(entry.value.data)
if let expirationTimestamp = entry.expirationTimestamp { var expirationTimestampValue: Int32 = entry.expirationTimestamp ?? 0
var expirationTimestampValue: Int32 = expirationTimestamp buffer.write(&expirationTimestampValue, length: 4)
buffer.write(&expirationTimestampValue, length: 4)
var flags: UInt8 = 0
if entry.isCloseFriends {
flags |= (1 << 0)
} }
buffer.write(&flags, length: 1)
self.valueBox.set(self.table, key: self.key(Key(peerId: peerId, id: entry.id)), value: buffer.readBufferNoCopy()) self.valueBox.set(self.table, key: self.key(Key(peerId: peerId, id: entry.id)), value: buffer.readBufferNoCopy())
} }

View File

@ -4481,12 +4481,12 @@ func replayFinalState(
if let currentIndex = updatedPeerEntries.firstIndex(where: { $0.id == storedItem.id }) { if let currentIndex = updatedPeerEntries.firstIndex(where: { $0.id == storedItem.id }) {
if case .item = storedItem { if case .item = storedItem {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries[currentIndex] = StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp) updatedPeerEntries[currentIndex] = StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends)
} }
} }
} else { } else {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp)) updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends))
} }
} }
} else { } else {

View File

@ -382,6 +382,15 @@ public enum Stories {
} }
} }
public var isCloseFriends: Bool {
switch self {
case let .item(item):
return item.isCloseFriends
case .placeholder:
return false
}
}
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
@ -886,7 +895,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
isEdited: item.isEdited isEdited: item.isEdited
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp)) items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends))
} }
updatedItems.append(updatedItem) updatedItems.append(updatedItem)
} }
@ -1064,7 +1073,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
isEdited: item.isEdited isEdited: item.isEdited
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp) items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
} }
updatedItems.append(updatedItem) updatedItems.append(updatedItem)
@ -1191,7 +1200,7 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory
isEdited: item.isEdited isEdited: item.isEdited
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp) items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
} }
updatedItems.append(updatedItem) updatedItems.append(updatedItem)
@ -1661,7 +1670,7 @@ public final class EngineStoryViewListContext {
isEdited: item.isEdited isEdited: item.isEdited
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp) currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
} }
} }
} }
@ -1838,7 +1847,7 @@ func _internal_refreshStories(account: Account, peerId: PeerId, ids: [Int32]) ->
if let updatedItem = result.first(where: { $0.id == currentItems[i].id }) { if let updatedItem = result.first(where: { $0.id == currentItems[i].id }) {
if case .item = updatedItem { if case .item = updatedItem {
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp) currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
} }
} }
} }

View File

@ -338,7 +338,7 @@ public final class StorySubscriptionsContext {
updatedPeerEntries.append(previousEntry) updatedPeerEntries.append(previousEntry)
} else { } else {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp)) updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends))
} }
} }
} }
@ -1014,7 +1014,7 @@ public final class PeerExpiringStoryListContext {
updatedPeerEntries.append(previousEntry) updatedPeerEntries.append(previousEntry)
} else { } else {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp)) updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends))
} }
} }
} }
@ -1180,7 +1180,7 @@ public func _internal_pollPeerStories(postbox: Postbox, network: Network, accoun
updatedPeerEntries.append(previousEntry) updatedPeerEntries.append(previousEntry)
} else { } else {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp)) updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends))
} }
} }
} }

View File

@ -1003,7 +1003,7 @@ public extension TelegramEngine {
isEdited: item.isEdited isEdited: item.isEdited
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp) currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
} }
} }
} }

View File

@ -43,15 +43,15 @@ func updatePeers(transaction: Transaction, accountPeerId: PeerId, peers: Accumul
for (_, user) in peers.users { for (_, user) in peers.users {
if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
parsedPeers.append(telegramUser) parsedPeers.append(telegramUser)
switch user { switch user {
case let .user(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, storiesMaxId): case let .user(flags, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, storiesMaxId):
let isMin = (flags & (1 << 20)) != 0
let storiesUnavailable = (flags2 & (1 << 4)) != 0
if let storiesMaxId = storiesMaxId { if let storiesMaxId = storiesMaxId {
transaction.setStoryItemsInexactMaxId(peerId: user.peerId, id: storiesMaxId) transaction.setStoryItemsInexactMaxId(peerId: user.peerId, id: storiesMaxId)
} else if !isMin && storiesUnavailable {
transaction.clearStoryItemsInexactMaxId(peerId: user.peerId)
} }
/*#if DEBUG
transaction.setStoryItemsInexactMaxId(peerId: user.peerId, id: 10)
#endif*/
case .userEmpty: case .userEmpty:
break break
} }

View File

@ -341,4 +341,5 @@ public enum PresentationResourceParameterKey: Hashable {
case statusAutoremoveIcon(isActive: Bool) case statusAutoremoveIcon(isActive: Bool)
case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType) case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
} }

View File

@ -1272,4 +1272,30 @@ public struct PresentationResourcesChat {
}) })
}) })
} }
public static func chatReplyStoryIndicatorIcon(_ theme: PresentationTheme, type: ChatExpiredStoryIndicatorType) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatReplyStoryIndicatorIcon(type: type), { theme in
return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let foregroundColor: UIColor
switch type {
case .incoming:
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor
case .outgoing:
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor
case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
}
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ReplyStoryIcon"), color: foregroundColor) {
UIGraphicsPushContext(context)
let fittedSize = image.size
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - fittedSize.width) * 0.5), y: floor((size.height - fittedSize.height) * 0.5)), size: fittedSize), blendMode: .normal, alpha: 1.0)
UIGraphicsPopContext()
}
})
})
}
} }

View File

@ -268,7 +268,7 @@ public final class AvatarStoryIndicatorComponent: Component {
let maxOuterInset = component.activeLineWidth * 2.0 let maxOuterInset = component.activeLineWidth * 2.0
diameter = availableSize.width + maxOuterInset * 2.0 diameter = availableSize.width + maxOuterInset * 2.0
let imageDiameter = availableSize.width + ceilToScreenPixels(maxOuterInset) * 2.0 let imageDiameter = ceil(availableSize.width + maxOuterInset * 2.0)
let activeColors: [CGColor] let activeColors: [CGColor]
let inactiveColors: [CGColor] let inactiveColors: [CGColor]

View File

@ -360,7 +360,7 @@ public final class PeerListItemComponent: Component {
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: storyStats.totalCount == 0 ? 0 : 1, totalCount: storyStats.totalCount == 0 ? 0 : 1,
unseenCount: storyStats.unseenCount == 0 ? 0 : 1, unseenCount: storyStats.unseenCount == 0 ? 0 : 1,
hasUnseenCloseFriendsItems: false hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(theme: component.theme), colors: AvatarNode.Colors(theme: component.theme),

View File

@ -916,6 +916,7 @@ public final class StoryContentContextImpl: StoryContentContext {
public final class SingleStoryContentContextImpl: StoryContentContext { public final class SingleStoryContentContextImpl: StoryContentContext {
private let context: AccountContext private let context: AccountContext
private let readGlobally: Bool
public private(set) var stateValue: StoryContentContextState? public private(set) var stateValue: StoryContentContextState?
public var state: Signal<StoryContentContextState, NoError> { public var state: Signal<StoryContentContextState, NoError> {
@ -935,9 +936,11 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
public init( public init(
context: AccountContext, context: AccountContext,
storyId: StoryId storyId: StoryId,
readGlobally: Bool
) { ) {
self.context = context self.context = context
self.readGlobally = readGlobally
self.storyDisposable = (combineLatest(queue: .mainQueue(), self.storyDisposable = (combineLatest(queue: .mainQueue(),
context.engine.data.subscribe( context.engine.data.subscribe(
@ -1084,6 +1087,9 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
} }
public func markAsSeen(id: StoryId) { public func markAsSeen(id: StoryId) {
if self.readGlobally {
let _ = self.context.engine.messages.markStoryAsSeen(peerId: id.peerId, id: id.id, asPinned: false).start()
}
} }
} }

View File

@ -94,15 +94,15 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
override var state: UIGestureRecognizer.State { override var state: UIGestureRecognizer.State {
didSet { didSet {
switch self.state { /*switch self.state {
case .began, .cancelled, .ended, .failed: case .cancelled, .ended, .failed:
if self.isTracking { if self.isTracking {
self.isTracking = false self.isTracking = false
self.updateIsTracking?(false) self.updateIsTracking?(self.isTracking)
} }
default: default:
break break
} }*/
} }
} }
@ -372,6 +372,8 @@ private final class StoryContainerScreenComponent: Component {
var longPressRecognizer: StoryLongPressRecognizer? var longPressRecognizer: StoryLongPressRecognizer?
private var pendingNavigationToItemId: (peerId: EnginePeer.Id, id: Int32)?
override init(frame: CGRect) { override init(frame: CGRect) {
self.backgroundLayer = SimpleLayer() self.backgroundLayer = SimpleLayer()
self.backgroundLayer.backgroundColor = UIColor.black.cgColor self.backgroundLayer.backgroundColor = UIColor.black.cgColor
@ -971,16 +973,19 @@ private final class StoryContainerScreenComponent: Component {
self.commitHorizontalPan(velocity: CGPoint(x: 200.0, y: 0.0)) self.commitHorizontalPan(velocity: CGPoint(x: 200.0, y: 0.0))
} }
} else { } else {
let mappedDirection: StoryContentContextNavigation.ItemDirection var mappedId: Int32?
switch direction { switch direction {
case .previous: case .previous:
mappedDirection = .previous mappedId = slice.previousItemId
case .next: case .next:
mappedDirection = .next mappedId = slice.nextItemId
case let .id(id): case let .id(id):
mappedDirection = .id(id) mappedId = id
}
if let mappedId {
self.pendingNavigationToItemId = (slice.peer.id, mappedId)
component.content.navigate(navigation: .item(.id(mappedId)))
} }
component.content.navigate(navigation: .item(mappedDirection))
} }
} }
} }
@ -1073,6 +1078,16 @@ private final class StoryContainerScreenComponent: Component {
self.component = component self.component = component
self.state = state self.state = state
if let pendingNavigationToItemId = self.pendingNavigationToItemId {
if let slice = component.content.stateValue?.slice, slice.peer.id == pendingNavigationToItemId.peerId {
if slice.item.storyItem.id == pendingNavigationToItemId.id {
self.pendingNavigationToItemId = nil
}
} else {
self.pendingNavigationToItemId = nil
}
}
transition.setFrame(view: self.transitionCloneMasterView, frame: CGRect(origin: CGPoint(), size: availableSize)) transition.setFrame(view: self.transitionCloneMasterView, frame: CGRect(origin: CGPoint(), size: availableSize))
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize)) transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize))
@ -1104,6 +1119,9 @@ private final class StoryContainerScreenComponent: Component {
if !environment.isVisible { if !environment.isVisible {
isProgressPaused = true isProgressPaused = true
} }
if self.pendingNavigationToItemId != nil {
isProgressPaused = true
}
var dismissPanOffset: CGFloat = 0.0 var dismissPanOffset: CGFloat = 0.0
var dismissPanScale: CGFloat = 1.0 var dismissPanScale: CGFloat = 1.0
@ -1291,10 +1309,21 @@ private final class StoryContainerScreenComponent: Component {
switch self.audioMode { switch self.audioMode {
case .ambient: case .ambient:
self.audioMode = .on if self.isMuteSwitchOn {
for (_, itemSetView) in self.visibleItemSetViews { self.audioMode = .off
if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
componentView.leaveAmbientMode() for (_, itemSetView) in self.visibleItemSetViews {
if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
componentView.enterAmbientMode(ambient: !self.isMuteSwitchOn)
}
}
} else {
self.audioMode = .on
for (_, itemSetView) in self.visibleItemSetViews {
if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
componentView.leaveAmbientMode()
}
} }
} }
case .on: case .on:

View File

@ -170,8 +170,16 @@ final class StoryItemContentComponent: Component {
return return
} }
var shouldLoop = false
if self.progressMode == .blurred { if self.progressMode == .blurred {
shouldLoop = true
} else if let component = self.component, component.item.isPending {
shouldLoop = true
}
if shouldLoop {
self.rewind() self.rewind()
if let videoNode = self.videoNode { if let videoNode = self.videoNode {
if self.contentLoaded { if self.contentLoaded {
videoNode.play() videoNode.play()
@ -251,12 +259,7 @@ final class StoryItemContentComponent: Component {
private func updateProgressMode(update: Bool) { private func updateProgressMode(update: Bool) {
if let videoNode = self.videoNode { if let videoNode = self.videoNode {
var canPlay = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy let canPlay = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy
if let component = self.component {
if component.item.isPending {
canPlay = false
}
}
if canPlay { if canPlay {
videoNode.play() videoNode.play()
@ -274,7 +277,10 @@ final class StoryItemContentComponent: Component {
var needsTimer = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy var needsTimer = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy
if let component = self.component { if let component = self.component {
if component.item.isPending { if component.item.isPending {
needsTimer = false if case .file = self.currentMessageMedia {
} else {
needsTimer = false
}
} }
} }

View File

@ -614,7 +614,18 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
if let captionItemView = self.captionItem?.view.view as? StoryContentCaptionComponent.View {
if captionItemView.hitTest(self.convert(point, to: captionItemView), with: nil) != nil {
return false
}
}
if self.controlsContainerView.frame.contains(point) { if self.controlsContainerView.frame.contains(point) {
if let result = self.controlsContainerView.hitTest(self.convert(point, to: self.controlsContainerView), with: nil) {
if result != self.controlsContainerView {
return false
}
}
return true return true
} }

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_chatstoryreply.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,162 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 1.000000 1.000000 cm
0.000000 0.000000 0.000000 scn
7.000000 12.800000 m
6.974974 12.800000 6.949986 12.799842 6.925035 12.799525 c
5.418858 12.780447 4.051039 12.187255 3.030348 11.228721 c
2.952010 11.155152 2.875716 11.079432 2.801562 11.001655 c
1.840759 9.993905 1.239081 8.640710 1.201836 7.147378 c
1.200614 7.098401 1.200000 7.049272 1.200000 7.000000 c
1.200000 6.974975 1.200159 6.949987 1.200475 6.925036 c
1.218704 5.485904 1.761072 4.173086 2.645467 3.168697 c
2.788374 3.006401 2.940211 2.852158 3.100211 2.706736 c
4.082876 1.813602 5.373415 1.253222 6.793827 1.203597 c
6.862257 1.201206 6.930987 1.200001 7.000000 1.200001 c
7.257290 1.200001 7.510377 1.216717 7.758291 1.249055 c
8.086879 1.291916 8.387997 1.060289 8.430859 0.731703 c
8.473720 0.403115 8.242093 0.101996 7.913507 0.059135 c
7.614261 0.020102 7.309328 0.000000 7.000000 0.000000 c
6.939593 0.000000 6.879366 0.000765 6.819326 0.002286 c
5.110955 0.045568 3.554689 0.700914 2.361623 1.757235 c
2.171016 1.925995 1.989679 2.104989 1.818473 2.293358 c
0.688624 3.536468 0.000000 5.187816 0.000000 7.000000 c
0.000000 8.859291 0.724890 10.549273 1.907470 11.802750 c
1.992771 11.893165 2.080454 11.981308 2.170417 12.067080 c
3.426557 13.264702 5.127409 14.000000 7.000000 14.000000 c
7.309328 14.000000 7.614261 13.979899 7.913506 13.940866 c
8.242093 13.898004 8.473720 13.596886 8.430858 13.268298 c
8.387997 12.939711 8.086878 12.708084 7.758291 12.750946 c
7.510377 12.783284 7.257290 12.800000 7.000000 12.800000 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 1.000000 12.462891 cm
0.000000 0.000000 0.000000 scn
10.420465 0.980462 m
10.622378 1.243212 10.999063 1.292530 11.261813 1.090617 c
11.746525 0.718136 12.181026 0.283636 12.553507 -0.201076 c
12.755420 -0.463827 12.706101 -0.840511 12.443351 -1.042424 c
12.180601 -1.244337 11.803917 -1.195019 11.602004 -0.932268 c
11.293073 -0.530255 10.932634 -0.169816 10.530622 0.139114 c
10.267871 0.341027 10.218553 0.717711 10.420465 0.980462 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 1.000000 12.052734 cm
0.000000 0.000000 0.000000 scn
13.268296 -2.621875 m
13.596884 -2.579013 13.898003 -2.810640 13.940864 -3.139227 c
13.979897 -3.438472 13.999999 -3.743406 13.999999 -4.052734 c
13.999999 -4.362062 13.979897 -4.666995 13.940864 -4.966240 c
13.898003 -5.294827 13.596884 -5.526454 13.268296 -5.483593 c
12.939710 -5.440731 12.708083 -5.139613 12.750944 -4.811026 c
12.783282 -4.563112 12.799998 -4.310025 12.799998 -4.052734 c
12.799998 -3.795443 12.783282 -3.542356 12.750944 -3.294442 c
12.708083 -2.965855 12.939710 -2.664736 13.268296 -2.621875 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 1.000000 12.462891 cm
0.000000 0.000000 0.000000 scn
12.443351 -7.883357 m
12.706101 -8.085270 12.755420 -8.461954 12.553507 -8.724705 c
12.181025 -9.209416 11.746525 -9.643917 11.261813 -10.016398 c
10.999063 -10.218311 10.622378 -10.168993 10.420465 -9.906242 c
10.218553 -9.643493 10.267871 -9.266809 10.530621 -9.064896 c
10.932634 -8.755964 11.293073 -8.395526 11.602003 -7.993513 c
11.803917 -7.730762 12.180601 -7.681444 12.443351 -7.883357 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 1.000000 8.817383 cm
0.000000 0.000000 0.000000 scn
6.618695 1.719694 m
6.618695 1.022617 l
9.362720 1.022617 10.293095 -0.953440 10.605222 -2.430902 c
10.706805 -2.911749 10.757596 -3.152171 10.711860 -3.231688 c
10.668221 -3.307556 10.609138 -3.344767 10.521865 -3.351348 c
10.430392 -3.358245 10.195158 -3.177592 9.724692 -2.816287 c
9.077857 -2.319534 8.082672 -1.857383 6.618695 -1.857383 c
6.618695 -2.554460 l
6.618695 -3.016783 6.618695 -3.247944 6.525906 -3.359222 c
6.445327 -3.455857 6.323976 -3.508946 6.198317 -3.502536 c
6.053617 -3.495155 5.883851 -3.338263 5.544319 -3.024477 c
3.231889 -0.887401 l
3.055273 -0.724178 2.966961 -0.642563 2.934227 -0.546968 c
2.905464 -0.462972 2.905464 -0.371794 2.934227 -0.287798 c
2.966961 -0.192202 3.055270 -0.110590 3.231889 0.052636 c
5.544319 2.189712 l
5.883851 2.503497 6.053617 2.660389 6.198317 2.667771 c
6.323976 2.674181 6.445327 2.621092 6.525906 2.524457 c
6.618695 2.413178 6.618695 2.182017 6.618695 1.719694 c
h
f
n
Q
endstream
endobj
3 0 obj
4202
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.000000 16.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000004292 00000 n
0000004315 00000 n
0000004488 00000 n
0000004562 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
4621
%%EOF

View File

@ -4531,7 +4531,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId) let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: false)
let _ = (storyContent.state let _ = (storyContent.state
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in |> deliverOnMainQueue).start(next: { [weak self] _ in
@ -4556,7 +4556,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
transitionIn = StoryContainerScreen.TransitionIn( transitionIn = StoryContainerScreen.TransitionIn(
sourceView: result, sourceView: result,
sourceRect: result.bounds, sourceRect: result.bounds,
sourceCornerRadius: 2.0, sourceCornerRadius: 6.0,
sourceIsAvatar: false sourceIsAvatar: false
) )
} }
@ -5005,9 +5005,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return nil return nil
} }
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: storyStats.totalCount == 0 ? 0 : 1, totalCount: storyStats.totalCount,
unseenCount: storyStats.unseenCount == 0 ? 0 : 1, unseenCount: storyStats.unseenCount,
hasUnseenCloseFriendsItems: false hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(theme: strongSelf.presentationData.theme), colors: AvatarNode.Colors(theme: strongSelf.presentationData.theme),

View File

@ -611,7 +611,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: storyStats.totalCount, totalCount: storyStats.totalCount,
unseenCount: storyStats.unseenCount, unseenCount: storyStats.unseenCount,
hasUnseenCloseFriendsItems: false hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(theme: theme), colors: AvatarNode.Colors(theme: theme),

View File

@ -113,6 +113,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
let isMedia: Bool let isMedia: Bool
let isText: Bool let isText: Bool
var isExpiredStory: Bool = false var isExpiredStory: Bool = false
var isStory: Bool = false
if let message = arguments.message { if let message = arguments.message {
let author = message.effectiveAuthor let author = message.effectiveAuthor
@ -143,6 +144,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
textString = NSAttributedString(string: "Expired story") textString = NSAttributedString(string: "Expired story")
isMedia = false isMedia = false
} else { } else {
isStory = true
textString = NSAttributedString(string: "Story") textString = NSAttributedString(string: "Story")
isMedia = true isMedia = true
} }
@ -188,7 +190,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
case let .bubble(incoming): case let .bubble(incoming):
titleColor = incoming ? (authorNameColor ?? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor) : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor 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) lineImage = incoming ? (authorNameColor.flatMap({ PresentationResourcesChat.chatBubbleVerticalLineImage(color: $0) }) ?? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(arguments.presentationData.theme.theme)) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(arguments.presentationData.theme.theme)
if isExpiredStory { if isExpiredStory || isStory {
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
} else if isMedia { } else if isMedia {
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
@ -301,19 +303,19 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0) let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets)) let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
if isExpiredStory { if isExpiredStory || isStory {
contrainedTextSize.width -= 24.0 contrainedTextSize.width -= 26.0
} }
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets)) let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
let imageSide: CGFloat let imageSide: CGFloat
imageSide = titleLayout.size.height + textLayout.size.height - 16.0 imageSide = titleLayout.size.height + textLayout.size.height - 13.0
var applyImage: (() -> TransformImageNode)? var applyImage: (() -> TransformImageNode)?
if let imageDimensions = imageDimensions { if let imageDimensions = imageDimensions {
let boundingSize = CGSize(width: imageSide, height: imageSide) let boundingSize = CGSize(width: imageSide, height: imageSide)
leftInset += imageSide + 2.0 leftInset += imageSide + 6.0
var radius: CGFloat = 2.0 var radius: CGFloat = 6.0
var imageSize = imageDimensions.aspectFilled(boundingSize) var imageSize = imageDimensions.aspectFilled(boundingSize)
if hasRoundImage { if hasRoundImage {
radius = boundingSize.width / 2.0 radius = boundingSize.width / 2.0
@ -359,8 +361,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
} }
var size = CGSize(width: max(titleLayout.size.width - textInsets.left - textInsets.right, textLayout.size.width - textInsets.left - textInsets.right) + leftInset, height: titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing) var size = CGSize(width: max(titleLayout.size.width - textInsets.left - textInsets.right, textLayout.size.width - textInsets.left - textInsets.right) + leftInset, height: titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing)
if isExpiredStory { if isExpiredStory || isStory {
size.width += 14.0 size.width += 16.0
} }
return (size, { attemptSynchronous in return (size, { attemptSynchronous in
@ -403,7 +405,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
node.addSubnode(imageNode) node.addSubnode(imageNode)
node.imageNode = imageNode node.imageNode = imageNode
} }
imageNode.frame = CGRect(origin: CGPoint(x: 8.0, y: 4.0 + UIScreenPixel), size: CGSize(width: imageSide, height: imageSide)) imageNode.frame = CGRect(origin: CGPoint(x: 8.0, y: 3.0), size: CGSize(width: imageSide, height: imageSide))
if let updateImageSignal = updateImageSignal { if let updateImageSignal = updateImageSignal {
imageNode.setSignal(updateImageSignal) imageNode.setSignal(updateImageSignal)
@ -419,9 +421,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size) titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size)
let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size) let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size)
textNode.textNode.frame = textFrame.offsetBy(dx: isExpiredStory ? 16.0 : 0.0, dy: 0.0) textNode.textNode.frame = textFrame.offsetBy(dx: (isExpiredStory || isStory) ? 18.0 : 0.0, dy: 0.0)
if isExpiredStory { if isExpiredStory || isStory {
let expiredStoryIconView: UIImageView let expiredStoryIconView: UIImageView
if let current = node.expiredStoryIconView { if let current = node.expiredStoryIconView {
expiredStoryIconView = current expiredStoryIconView = current
@ -439,10 +441,20 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
imageType = incoming ? .incoming : .outgoing imageType = incoming ? .incoming : .outgoing
} }
expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType) if isExpiredStory {
expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
} else {
expiredStoryIconView.image = PresentationResourcesChat.chatReplyStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
}
if let image = expiredStoryIconView.image { if let image = expiredStoryIconView.image {
let imageSize = CGSize(width: floor(image.size.width * 1.22), height: floor(image.size.height * 1.22)) let imageSize: CGSize
expiredStoryIconView.frame = CGRect(origin: CGPoint(x: textFrame.minX - 2.0, y: textFrame.minY + 2.0), size: imageSize) if isExpiredStory {
imageSize = CGSize(width: floor(image.size.width * 1.22), height: floor(image.size.height * 1.22))
expiredStoryIconView.frame = CGRect(origin: CGPoint(x: textFrame.minX - 2.0, y: textFrame.minY + 2.0), size: imageSize)
} else {
imageSize = image.size
expiredStoryIconView.frame = CGRect(origin: CGPoint(x: textFrame.minX - 1.0, y: textFrame.minY + 3.0 + UIScreenPixel), size: imageSize)
}
} }
} else if let expiredStoryIconView = node.expiredStoryIconView { } else if let expiredStoryIconView = node.expiredStoryIconView {
expiredStoryIconView.removeFromSuperview() expiredStoryIconView.removeFromSuperview()

View File

@ -209,7 +209,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode {
if let story, let selectedMedia { if let story, let selectedMedia {
if mediaUpdated { if mediaUpdated {
if story.isForwardingDisabled { if story.isForwardingDisabled {
let maxImageSize = CGSize(width: 180.0, height: 180.0) let maxImageSize = CGSize(width: 180.0, height: 180.0).aspectFitted(imageSize)
let boundingImageSize = maxImageSize let boundingImageSize = maxImageSize
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?

View File

@ -37,7 +37,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
if let story { if let story {
let navigationController = params.navigationController let navigationController = params.navigationController
let context = params.context let context = params.context
let storyContent = SingleStoryContentContextImpl(context: params.context, storyId: story.storyId) let storyContent = SingleStoryContentContextImpl(context: params.context, storyId: story.storyId, readGlobally: story.isMention)
let _ = (storyContent.state let _ = (storyContent.state
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in |> deliverOnMainQueue).start(next: { [weak navigationController] _ in

View File

@ -814,7 +814,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
} }
|> deliverOnMainQueue).start(next: { exists in |> deliverOnMainQueue).start(next: { exists in
if exists { if exists {
let storyContent = SingleStoryContentContextImpl(context: context, storyId: StoryId(peerId: peerId, id: id)) let storyContent = SingleStoryContentContextImpl(context: context, storyId: StoryId(peerId: peerId, id: id), readGlobally: false)
let _ = (storyContent.state let _ = (storyContent.state
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in |> deliverOnMainQueue).start(next: { [weak navigationController] _ in

View File

@ -423,7 +423,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
private let playbackStartDisposable = MetaDisposable() private let playbackStartDisposable = MetaDisposable()
var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool)? var storyData: (totalCount: Int, unseenCount: Int, hasUnseenCloseFriends: Bool)?
init(context: AccountContext) { init(context: AccountContext) {
self.context = context self.context = context
@ -464,8 +464,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
] ]
self.avatarNode.setStoryStats(storyStats: self.storyData.flatMap { storyData in self.avatarNode.setStoryStats(storyStats: self.storyData.flatMap { storyData in
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: 1, totalCount: storyData.totalCount,
unseenCount: storyData.hasUnseen ? 1 : 0, unseenCount: storyData.unseenCount,
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(

View File

@ -3895,7 +3895,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.headerNode.avatarListNode.avatarContainerNode.storyData = nil self.headerNode.avatarListNode.avatarContainerNode.storyData = nil
self.headerNode.avatarListNode.listContainerNode.storyParams = nil self.headerNode.avatarListNode.listContainerNode.storyParams = nil
} else { } else {
self.headerNode.avatarListNode.avatarContainerNode.storyData = (state.hasUnseen, state.hasUnseenCloseFriends && peer.id != self.context.account.peerId) let totalCount = state.items.count
var unseenCount = 0
for item in state.items {
if item.id > state.maxReadId {
unseenCount += 1
}
}
self.headerNode.avatarListNode.avatarContainerNode.storyData = (totalCount, unseenCount, state.hasUnseenCloseFriends && peer.id != self.context.account.peerId)
self.headerNode.avatarListNode.listContainerNode.storyParams = (peer, state.items.prefix(3).compactMap { item -> EngineStoryItem? in self.headerNode.avatarListNode.listContainerNode.storyParams = (peer, state.items.prefix(3).compactMap { item -> EngineStoryItem? in
switch item { switch item {
case let .item(item): case let .item(item):

View File

@ -129,7 +129,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} }
self.animatedStickerNode = nil self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
displayUndo = undo displayUndo = undo
self.originalRemainingSeconds = 5 self.originalRemainingSeconds = 5
case let .hidArchive(title, text, undo): case let .hidArchive(title, text, undo):