diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 305fc60182..4725988abe 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1881,24 +1881,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController 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)) Queue.mainQueue().after(1.0, { [weak self] in @@ -1935,12 +1917,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController unseenCount += 1 } } + let hasUnseenCloseFriends = rawStoryArchiveSubscriptions.items.contains(where: { $0.hasUnseenCloseFriends }) archiveStoryState = ChatListNodeState.StoryState( stats: EngineChatList.StoryStats( 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 { return } + let undoValue: Bool if self.location == .chatList(groupId: .archive) { self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false) + undoValue = true } else { 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 } @@ -2851,7 +2850,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController location: .point(location, .bottom), shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) } ) - self.present(tooltipController, in: .window(.root)) + self.present(tooltipController, in: .window(.root))*/ }))) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index bb6980b673..62fcbedaae 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2107,6 +2107,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else if let action = media as? TelegramMediaAction, case let .suggestedProfilePhoto(image) = action.action, let _ = image { let fitSize = contentImageSize 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?() - 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 messageTypeIconOffset = mediaPreviewOffset diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index ae22930744..d35456160d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -697,7 +697,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, storyState: entry.renderedPeer.peerId == accountPeerId ? nil : entry.storyStats.flatMap { stats -> ChatListNodeState.StoryState in return ChatListNodeState.StoryState( stats: stats, - hasUnseenCloseFriends: false + hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) } )) diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 79858fa469..7f22b292db 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -1187,7 +1187,7 @@ open class TextNode: ASDisplayNode { if brokenLineRange.location + brokenLineRange.length > attributedString.length { brokenLineRange.length = attributedString.length - brokenLineRange.location } - if lineRange.length == 0 { + if lineRange.length == 0 && !didClipLinebreak { break } @@ -1202,7 +1202,11 @@ open class TextNode: ASDisplayNode { let truncatedTokenString: NSAttributedString 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 { var truncationTokenAttributes: [NSAttributedString.Key : AnyObject] = [:] truncationTokenAttributes[NSAttributedString.Key.font] = font diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index a4604011e7..440b8785a7 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -1358,7 +1358,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo return AvatarNode.StoryStats( totalCount: storyStats.totalCount, unseenCount: storyStats.unseenCount, - hasUnseenCloseFriendsItems: false + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends ) }, presentationParams: AvatarNode.StoryPresentationParams( colors: AvatarNode.Colors(theme: item.presentationData.theme), diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 8175b030d5..b3ac656894 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -260,10 +260,12 @@ public enum ChatListEntry: Comparable { public struct PeerStoryStats: Equatable { public var totalCount: 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.unseenCount = unseenCount + self.hasUnseenCloseFriends = hasUnseenCloseFriends } } @@ -282,9 +284,9 @@ func fetchPeerStoryStats(postbox: PostboxImpl, peerId: PeerId) -> PeerStoryStats if topItems.isExact { 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 { - return PeerStoryStats(totalCount: 1, unseenCount: topItems.id > maxSeenId ? 1 : 0) + return PeerStoryStats(totalCount: 1, unseenCount: topItems.id > maxSeenId ? 1 : 0, hasUnseenCloseFriends: false) } } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 175104a5da..8fb8dbd80a 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1311,6 +1311,11 @@ public final class Transaction { 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] { 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] { return self.storyItemsTable.get(peerId: peerId) } diff --git a/submodules/Postbox/Sources/StoryItemsTable.swift b/submodules/Postbox/Sources/StoryItemsTable.swift index 783886f1a8..f7b4a3770a 100644 --- a/submodules/Postbox/Sources/StoryItemsTable.swift +++ b/submodules/Postbox/Sources/StoryItemsTable.swift @@ -4,15 +4,18 @@ public final class StoryItemsTableEntry: Equatable { public let value: CodableEntry public let id: Int32 public let expirationTimestamp: Int32? + public let isCloseFriends: Bool public init( value: CodableEntry, id: Int32, - expirationTimestamp: Int32? + expirationTimestamp: Int32?, + isCloseFriends: Bool ) { self.value = value self.id = id self.expirationTimestamp = expirationTimestamp + self.isCloseFriends = isCloseFriends } public static func ==(lhs: StoryItemsTableEntry, rhs: StoryItemsTableEntry) -> Bool { @@ -28,6 +31,9 @@ public final class StoryItemsTableEntry: Equatable { if lhs.expirationTimestamp != rhs.expirationTimestamp { return false } + if lhs.isCloseFriends != rhs.isCloseFriends { + return false + } return true } } @@ -133,22 +139,53 @@ final class StoryItemsTable: Table { 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 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) total += 1 if id > maxSeenId { 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 }, limit: 10000) - return (total, unseen) + return (total, unseen, hasUnseenCloseFriends) } public func get(peerId: PeerId) -> [StoryItemsTableEntry] { @@ -161,6 +198,7 @@ final class StoryItemsTable: Table { let entry: CodableEntry var expirationTimestamp: Int32? + var isCloseFriends = false let readBuffer = ReadBuffer(data: value.makeData()) var magic: UInt32 = 0 @@ -173,16 +211,41 @@ final class StoryItemsTable: Table { if readBuffer.offset + 4 <= readBuffer.length { var expirationTimestampValue: Int32 = 0 readBuffer.read(&expirationTimestampValue, offset: 0, length: 4) - expirationTimestamp = expirationTimestampValue + if expirationTimestampValue != 0 { + expirationTimestamp = expirationTimestampValue + } } } else { 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 { + assertionFailure() 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 }, limit: 10000) @@ -209,7 +272,22 @@ final class StoryItemsTable: Table { if readBuffer.offset + 4 <= readBuffer.length { var expirationTimestampValue: Int32 = 0 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 { var expirationTimestampValue: Int32 = 0 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 { buffer.reset() - var magic: UInt32 = 0xabcd1234 + var magic: UInt32 = 0xabcd1235 buffer.write(&magic, length: 4) var length: Int32 = Int32(entry.value.data.count) buffer.write(&length, length: 4) buffer.write(entry.value.data) - if let expirationTimestamp = entry.expirationTimestamp { - var expirationTimestampValue: Int32 = expirationTimestamp - buffer.write(&expirationTimestampValue, length: 4) + var expirationTimestampValue: Int32 = entry.expirationTimestamp ?? 0 + 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()) } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 40b27b0340..e10e1dfb5f 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -4481,12 +4481,12 @@ func replayFinalState( if let currentIndex = updatedPeerEntries.firstIndex(where: { $0.id == storedItem.id }) { if case .item = 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 { 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 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 60bfed09a9..cdc7651981 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -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 { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -886,7 +895,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId isEdited: item.isEdited ) 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) } @@ -1064,7 +1073,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor isEdited: item.isEdited ) 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) @@ -1191,7 +1200,7 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory isEdited: item.isEdited ) 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) @@ -1661,7 +1670,7 @@ public final class EngineStoryViewListContext { isEdited: item.isEdited )) 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 case .item = 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) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index fb2a491900..476a19efe6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -338,7 +338,7 @@ public final class StorySubscriptionsContext { updatedPeerEntries.append(previousEntry) } else { 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) } else { 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) } else { 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)) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index ccf4a09496..7ec1ca530a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1003,7 +1003,7 @@ public extension TelegramEngine { isEdited: item.isEdited )) 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) } } } diff --git a/submodules/TelegramCore/Sources/UpdatePeers.swift b/submodules/TelegramCore/Sources/UpdatePeers.swift index 1c56a05350..a2dd3ffa05 100644 --- a/submodules/TelegramCore/Sources/UpdatePeers.swift +++ b/submodules/TelegramCore/Sources/UpdatePeers.swift @@ -43,15 +43,15 @@ func updatePeers(transaction: Transaction, accountPeerId: PeerId, peers: Accumul for (_, user) in peers.users { if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { parsedPeers.append(telegramUser) - 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 { 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: break } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 36619e64be..188b00ec10 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -341,4 +341,5 @@ public enum PresentationResourceParameterKey: Hashable { case statusAutoremoveIcon(isActive: Bool) case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType) + case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 5a4fb91dd3..8f28cc78b7 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -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() + } + }) + }) + } } diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index a8caa00a9f..736fa2ba28 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -268,7 +268,7 @@ public final class AvatarStoryIndicatorComponent: Component { let maxOuterInset = component.activeLineWidth * 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 inactiveColors: [CGColor] diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift index edfc9f1b98..197af1d3a8 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift @@ -360,7 +360,7 @@ public final class PeerListItemComponent: Component { return AvatarNode.StoryStats( totalCount: storyStats.totalCount == 0 ? 0 : 1, unseenCount: storyStats.unseenCount == 0 ? 0 : 1, - hasUnseenCloseFriendsItems: false + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends ) }, presentationParams: AvatarNode.StoryPresentationParams( colors: AvatarNode.Colors(theme: component.theme), diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 9cc21f806e..6dd318fe50 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -916,6 +916,7 @@ public final class StoryContentContextImpl: StoryContentContext { public final class SingleStoryContentContextImpl: StoryContentContext { private let context: AccountContext + private let readGlobally: Bool public private(set) var stateValue: StoryContentContextState? public var state: Signal { @@ -935,9 +936,11 @@ public final class SingleStoryContentContextImpl: StoryContentContext { public init( context: AccountContext, - storyId: StoryId + storyId: StoryId, + readGlobally: Bool ) { self.context = context + self.readGlobally = readGlobally self.storyDisposable = (combineLatest(queue: .mainQueue(), context.engine.data.subscribe( @@ -1084,6 +1087,9 @@ public final class SingleStoryContentContextImpl: StoryContentContext { } public func markAsSeen(id: StoryId) { + if self.readGlobally { + let _ = self.context.engine.messages.markStoryAsSeen(peerId: id.peerId, id: id.id, asPinned: false).start() + } } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 7ebbebd468..bde1237011 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -94,15 +94,15 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer { override var state: UIGestureRecognizer.State { didSet { - switch self.state { - case .began, .cancelled, .ended, .failed: + /*switch self.state { + case .cancelled, .ended, .failed: if self.isTracking { self.isTracking = false - self.updateIsTracking?(false) + self.updateIsTracking?(self.isTracking) } default: break - } + }*/ } } @@ -372,6 +372,8 @@ private final class StoryContainerScreenComponent: Component { var longPressRecognizer: StoryLongPressRecognizer? + private var pendingNavigationToItemId: (peerId: EnginePeer.Id, id: Int32)? + override init(frame: CGRect) { self.backgroundLayer = SimpleLayer() 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)) } } else { - let mappedDirection: StoryContentContextNavigation.ItemDirection + var mappedId: Int32? switch direction { case .previous: - mappedDirection = .previous + mappedId = slice.previousItemId case .next: - mappedDirection = .next + mappedId = slice.nextItemId 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.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(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize)) @@ -1104,6 +1119,9 @@ private final class StoryContainerScreenComponent: Component { if !environment.isVisible { isProgressPaused = true } + if self.pendingNavigationToItemId != nil { + isProgressPaused = true + } var dismissPanOffset: CGFloat = 0.0 var dismissPanScale: CGFloat = 1.0 @@ -1291,10 +1309,21 @@ private final class StoryContainerScreenComponent: Component { switch self.audioMode { case .ambient: - self.audioMode = .on - for (_, itemSetView) in self.visibleItemSetViews { - if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { - componentView.leaveAmbientMode() + if self.isMuteSwitchOn { + self.audioMode = .off + + 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: diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 2a15c765bc..578a62c639 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -170,8 +170,16 @@ final class StoryItemContentComponent: Component { return } + var shouldLoop = false if self.progressMode == .blurred { + shouldLoop = true + } else if let component = self.component, component.item.isPending { + shouldLoop = true + } + + if shouldLoop { self.rewind() + if let videoNode = self.videoNode { if self.contentLoaded { videoNode.play() @@ -251,12 +259,7 @@ final class StoryItemContentComponent: Component { private func updateProgressMode(update: Bool) { if let videoNode = self.videoNode { - var canPlay = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy - if let component = self.component { - if component.item.isPending { - canPlay = false - } - } + let canPlay = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy if canPlay { videoNode.play() @@ -274,7 +277,10 @@ final class StoryItemContentComponent: Component { var needsTimer = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy if let component = self.component { if component.item.isPending { - needsTimer = false + if case .file = self.currentMessageMedia { + } else { + needsTimer = false + } } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index ab747f123c..552ef0a56c 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -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 let result = self.controlsContainerView.hitTest(self.convert(point, to: self.controlsContainerView), with: nil) { + if result != self.controlsContainerView { + return false + } + } return true } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyStoryIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyStoryIcon.imageset/Contents.json new file mode 100644 index 0000000000..32de9358a7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyStoryIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_chatstoryreply.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyStoryIcon.imageset/ic_chatstoryreply.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyStoryIcon.imageset/ic_chatstoryreply.pdf new file mode 100644 index 0000000000..cc7fc28d27 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyStoryIcon.imageset/ic_chatstoryreply.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index eae7a1cc40..66908efe7f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4531,7 +4531,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId) + let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: false) let _ = (storyContent.state |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in @@ -4556,7 +4556,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G transitionIn = StoryContainerScreen.TransitionIn( sourceView: result, sourceRect: result.bounds, - sourceCornerRadius: 2.0, + sourceCornerRadius: 6.0, sourceIsAvatar: false ) } @@ -5005,9 +5005,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return nil } return AvatarNode.StoryStats( - totalCount: storyStats.totalCount == 0 ? 0 : 1, - unseenCount: storyStats.unseenCount == 0 ? 0 : 1, - hasUnseenCloseFriendsItems: false + totalCount: storyStats.totalCount, + unseenCount: storyStats.unseenCount, + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends ) }, presentationParams: AvatarNode.StoryPresentationParams( colors: AvatarNode.Colors(theme: strongSelf.presentationData.theme), diff --git a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift index 109d552e69..bb601c6744 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift @@ -611,7 +611,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { return AvatarNode.StoryStats( totalCount: storyStats.totalCount, unseenCount: storyStats.unseenCount, - hasUnseenCloseFriendsItems: false + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends ) }, presentationParams: AvatarNode.StoryPresentationParams( colors: AvatarNode.Colors(theme: theme), diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index 60f9279f37..ed5d1ec224 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -113,6 +113,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { let isMedia: Bool let isText: Bool var isExpiredStory: Bool = false + var isStory: Bool = false if let message = arguments.message { let author = message.effectiveAuthor @@ -143,6 +144,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { textString = NSAttributedString(string: "Expired story") isMedia = false } else { + isStory = true textString = NSAttributedString(string: "Story") isMedia = true } @@ -188,7 +190,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { case let .bubble(incoming): titleColor = incoming ? (authorNameColor ?? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor) : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor lineImage = incoming ? (authorNameColor.flatMap({ PresentationResourcesChat.chatBubbleVerticalLineImage(color: $0) }) ?? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(arguments.presentationData.theme.theme)) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(arguments.presentationData.theme.theme) - if isExpiredStory { + if isExpiredStory || isStory { textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor } else if isMedia { textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor @@ -301,19 +303,19 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { 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)) - if isExpiredStory { - contrainedTextSize.width -= 24.0 + if isExpiredStory || isStory { + 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 imageSide: CGFloat - imageSide = titleLayout.size.height + textLayout.size.height - 16.0 + imageSide = titleLayout.size.height + textLayout.size.height - 13.0 var applyImage: (() -> TransformImageNode)? if let imageDimensions = imageDimensions { let boundingSize = CGSize(width: imageSide, height: imageSide) - leftInset += imageSide + 2.0 - var radius: CGFloat = 2.0 + leftInset += imageSide + 6.0 + var radius: CGFloat = 6.0 var imageSize = imageDimensions.aspectFilled(boundingSize) if hasRoundImage { 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) - if isExpiredStory { - size.width += 14.0 + if isExpiredStory || isStory { + size.width += 16.0 } return (size, { attemptSynchronous in @@ -403,7 +405,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { node.addSubnode(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 { 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) 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 if let current = node.expiredStoryIconView { expiredStoryIconView = current @@ -439,10 +441,20 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { 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 { - let 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) + let imageSize: CGSize + 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 { expiredStoryIconView.removeFromSuperview() diff --git a/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift index c98b83e36c..e6cc1345f0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift @@ -209,7 +209,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { if let story, let selectedMedia { if mediaUpdated { 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 var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 303fae7c12..9169242401 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -37,7 +37,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { if let story { let navigationController = params.navigationController 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 |> take(1) |> deliverOnMainQueue).start(next: { [weak navigationController] _ in diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index a309ebbf59..6080a8b5bb 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -814,7 +814,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur } |> deliverOnMainQueue).start(next: { exists in 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 |> take(1) |> deliverOnMainQueue).start(next: { [weak navigationController] _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index db5bef87f0..20f7ed27d1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -423,7 +423,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { private let playbackStartDisposable = MetaDisposable() - var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool)? + var storyData: (totalCount: Int, unseenCount: Int, hasUnseenCloseFriends: Bool)? init(context: AccountContext) { self.context = context @@ -464,8 +464,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { ] self.avatarNode.setStoryStats(storyStats: self.storyData.flatMap { storyData in return AvatarNode.StoryStats( - totalCount: 1, - unseenCount: storyData.hasUnseen ? 1 : 0, + totalCount: storyData.totalCount, + unseenCount: storyData.unseenCount, hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends ) }, presentationParams: AvatarNode.StoryPresentationParams( diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 2689835a83..11e51481eb 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3895,7 +3895,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.headerNode.avatarListNode.avatarContainerNode.storyData = nil self.headerNode.avatarListNode.listContainerNode.storyParams = nil } 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 switch item { case let .item(item): diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 20d8699e14..2d9ae2baef 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -129,7 +129,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } self.animatedStickerNode = nil 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 self.originalRemainingSeconds = 5 case let .hidArchive(title, text, undo):