Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-07-12 18:45:31 +02:00
commit e2afc7304c
38 changed files with 601 additions and 134 deletions

View File

@ -936,6 +936,12 @@ public final class AvatarNode: ASDisplayNode {
public func pushLoadingStatus(signal: Signal<Never, NoError>) -> Disposable {
let disposable = MetaDisposable()
for d in self.loadingStatuses.copyItems() {
d.dispose()
}
self.loadingStatuses.removeAll()
let index = self.loadingStatuses.add(disposable)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in

View File

@ -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))*/
})))
}

View File

@ -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

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
return ChatListNodeState.StoryState(
stats: stats,
hasUnseenCloseFriends: false
hasUnseenCloseFriends: stats.hasUnseenCloseFriends
)
}
))

View File

@ -1064,4 +1064,18 @@ public struct Transition {
)
}
}
public func animateContentsImage(layer: CALayer, from fromImage: CGImage, to toImage: CGImage, duration: Double, curve: Transition.Animation.Curve, completion: ((Bool) -> Void)? = nil) {
layer.animate(
from: fromImage,
to: toImage,
keyPath: "contents",
duration: duration,
delay: 0.0,
curve: .easeInOut,
removeOnCompletion: true,
additive: false,
completion: completion
)
}
}

View File

@ -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

View File

@ -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),

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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())
}

View File

@ -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 {

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 {
let container = try decoder.container(keyedBy: CodingKeys.self)
@ -888,7 +897,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)
}
@ -1066,7 +1075,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)
@ -1193,7 +1202,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)
@ -1663,7 +1672,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)
}
}
}
@ -1840,7 +1849,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)
}
}
}

View File

@ -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))
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -341,4 +341,5 @@ public enum PresentationResourceParameterKey: Hashable {
case statusAutoremoveIcon(isActive: Bool)
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
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]

View File

@ -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),

View File

@ -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<StoryContentContextState, NoError> {
@ -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()
}
}
}

View File

@ -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:

View File

@ -125,10 +125,10 @@ final class StoryContentCaptionComponent: Component {
private let scrollFullMaskView: UIView
private let scrollCenterMaskView: UIView
private let scrollBottomMaskView: UIImageView
private let scrollBottomFullMaskView: UIView
private let scrollTopMaskView: UIImageView
private let shadowGradientLayer: SimpleGradientLayer
private let shadowPlainLayer: SimpleLayer
private let shadowGradientView: UIImageView
private var component: StoryContentCaptionComponent?
private weak var state: EmptyComponentState?
@ -140,9 +140,15 @@ final class StoryContentCaptionComponent: Component {
private var isExpanded: Bool = false
private static let shadowImage: UIImage? = {
UIImage(named: "Stories/PanelGradient")
}()
override init(frame: CGRect) {
self.shadowGradientLayer = SimpleGradientLayer()
self.shadowPlainLayer = SimpleLayer()
self.shadowGradientView = UIImageView()
if let image = StoryContentCaptionComponent.View.shadowImage {
self.shadowGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0))
}
self.scrollViewContainer = UIView()
@ -170,6 +176,10 @@ final class StoryContentCaptionComponent: Component {
], locations: [0.0, 1.0]))
self.scrollMaskContainer.addSubview(self.scrollBottomMaskView)
self.scrollBottomFullMaskView = UIView()
self.scrollBottomFullMaskView.backgroundColor = .white
self.scrollMaskContainer.addSubview(self.scrollBottomFullMaskView)
self.scrollTopMaskView = UIImageView(image: generateGradientImage(size: CGSize(width: 8.0, height: 8.0), colors: [
UIColor(white: 1.0, alpha: 0.0),
UIColor(white: 1.0, alpha: 1.0)
@ -181,8 +191,7 @@ final class StoryContentCaptionComponent: Component {
super.init(frame: frame)
self.layer.addSublayer(self.shadowGradientLayer)
self.layer.addSublayer(self.shadowPlainLayer)
self.addSubview(self.shadowGradientView)
self.scrollViewContainer.addSubview(self.scrollView)
self.scrollView.delegate = self
@ -258,8 +267,7 @@ final class StoryContentCaptionComponent: Component {
let shadowOverflow: CGFloat = 58.0
let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow))
transition.setFrame(layer: self.shadowGradientLayer, frame: shadowFrame)
transition.setFrame(layer: self.shadowPlainLayer, frame: CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.maxY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0)))
transition.setFrame(view: self.shadowGradientView, frame: CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0)))
let expandDistance: CGFloat = 50.0
var expandFraction: CGFloat = self.scrollView.contentOffset.y / expandDistance
@ -626,7 +634,7 @@ final class StoryContentCaptionComponent: Component {
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
transition.setFrame(view: self.scrollViewContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
if self.shadowGradientLayer.colors == nil {
/*if self.shadowGradientLayer.colors == nil {
var locations: [NSNumber] = []
var colors: [CGColor] = []
let numStops = 10
@ -646,7 +654,7 @@ final class StoryContentCaptionComponent: Component {
self.shadowGradientLayer.type = .axial
self.shadowPlainLayer.backgroundColor = UIColor(white: 0.0, alpha: baseAlpha).cgColor
}
}*/
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
@ -656,6 +664,7 @@ final class StoryContentCaptionComponent: Component {
transition.setFrame(view: self.scrollFullMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: gradientEdgeHeight), size: CGSize(width: availableSize.width, height: availableSize.height - gradientEdgeHeight)))
transition.setFrame(view: self.scrollCenterMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: gradientEdgeHeight), size: CGSize(width: availableSize.width, height: availableSize.height - gradientEdgeHeight * 2.0)))
transition.setFrame(view: self.scrollBottomMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - gradientEdgeHeight), size: CGSize(width: availableSize.width, height: gradientEdgeHeight)))
transition.setFrame(view: self.scrollBottomFullMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - gradientEdgeHeight), size: CGSize(width: availableSize.width, height: gradientEdgeHeight)))
transition.setFrame(view: self.scrollTopMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: gradientEdgeHeight)))
self.ignoreExternalState = false
@ -685,8 +694,10 @@ final class StoryContentCaptionComponent: Component {
isExpandedTransition.setAlpha(view: dustNode.view, alpha: !self.isExpanded ? 0.0 : 1.0)
}
isExpandedTransition.setAlpha(layer: self.shadowPlainLayer, alpha: self.isExpanded ? 0.0 : 1.0)
isExpandedTransition.setAlpha(layer: self.shadowGradientLayer, alpha: self.isExpanded ? 0.0 : 1.0)
isExpandedTransition.setAlpha(view: self.shadowGradientView, alpha: self.isExpanded ? 0.0 : 1.0)
isExpandedTransition.setAlpha(view: self.scrollBottomMaskView, alpha: self.isExpanded ? 1.0 : 0.0)
isExpandedTransition.setAlpha(view: self.scrollBottomFullMaskView, alpha: self.isExpanded ? 0.0 : 1.0)
return availableSize
}

View File

@ -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
}
}
}

View File

@ -327,6 +327,10 @@ public final class StoryItemSetContainerComponent: Component {
}
}
private static let shadowImage: UIImage? = {
UIImage(named: "Stories/PanelGradient")
}()
public final class View: UIView, UIScrollViewDelegate, UIGestureRecognizerDelegate {
let sendMessageContext: StoryItemSetContainerSendMessage
@ -334,7 +338,7 @@ public final class StoryItemSetContainerComponent: Component {
let itemsContainerView: UIView
let controlsContainerView: UIView
let topContentGradientLayer: SimpleGradientLayer
let topContentGradientView: UIImageView
let bottomContentGradientLayer: SimpleGradientLayer
let contentDimView: UIView
@ -417,7 +421,12 @@ public final class StoryItemSetContainerComponent: Component {
self.controlsContainerView.layer.cornerCurve = .continuous
}
self.topContentGradientLayer = SimpleGradientLayer()
self.topContentGradientView = UIImageView()
if let image = StoryItemSetContainerComponent.shadowImage {
self.topContentGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0))
self.topContentGradientView.transform = CGAffineTransformMakeScale(1.0, -1.0)
}
self.bottomContentGradientLayer = SimpleGradientLayer()
self.contentDimView = UIView()
@ -441,7 +450,7 @@ public final class StoryItemSetContainerComponent: Component {
self.addSubview(self.controlsContainerView)
self.controlsContainerView.addSubview(self.contentDimView)
self.controlsContainerView.layer.addSublayer(self.topContentGradientLayer)
self.controlsContainerView.addSubview(self.topContentGradientView)
self.layer.addSublayer(self.bottomContentGradientLayer)
self.closeButton.addSubview(self.closeButtonIconView)
@ -605,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
}
@ -1257,7 +1277,7 @@ public final class StoryItemSetContainerComponent: Component {
}
self.closeButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
self.topContentGradientLayer.animateAlpha(from: 0.0, to: CGFloat(self.topContentGradientLayer.opacity), duration: 0.25)
self.topContentGradientView.layer.animateAlpha(from: 0.0, to: self.topContentGradientView.alpha, duration: 0.25)
let sourceLocalFrame = sourceView.convert(transitionIn.sourceRect, to: self)
let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - contentContainerView.frame.minX, y: sourceLocalFrame.minY - contentContainerView.frame.minY), size: sourceLocalFrame.size)
@ -1385,7 +1405,7 @@ public final class StoryItemSetContainerComponent: Component {
captionView.layer.animateAlpha(from: captionView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false)
}
self.closeButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
self.topContentGradientLayer.animateAlpha(from: CGFloat(self.topContentGradientLayer.opacity), to: 0.0, duration: 0.25, removeOnCompletion: false)
self.topContentGradientView.layer.animateAlpha(from: self.topContentGradientView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false)
if let leftInfoView = self.leftInfoItem?.view.view {
if transitionOut.destinationIsAvatar {
@ -1624,7 +1644,7 @@ public final class StoryItemSetContainerComponent: Component {
resetScrollingOffsetWithItemTransition = true
}
if self.topContentGradientLayer.colors == nil {
/*if self.topContentGradientLayer.colors == nil {
var locations: [NSNumber] = []
var colors: [CGColor] = []
let numStops = 4
@ -1642,7 +1662,7 @@ public final class StoryItemSetContainerComponent: Component {
self.topContentGradientLayer.locations = locations
self.topContentGradientLayer.colors = colors
self.topContentGradientLayer.type = .axial
}
}*/
if self.bottomContentGradientLayer.colors == nil {
var locations: [NSNumber] = []
var colors: [CGColor] = []
@ -1662,7 +1682,7 @@ public final class StoryItemSetContainerComponent: Component {
self.bottomContentGradientLayer.colors = colors
self.bottomContentGradientLayer.type = .axial
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3)
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.6)
}
let wasPanning = self.component?.isPanning ?? false
@ -2569,9 +2589,10 @@ public final class StoryItemSetContainerComponent: Component {
}
}
let gradientHeight: CGFloat = 74.0
transition.setFrame(layer: self.topContentGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: gradientHeight)))
transition.setAlpha(layer: self.topContentGradientLayer, alpha: (component.hideUI || self.displayViewList || self.isEditingStory) ? 0.0 : 1.0)
let topGradientHeight: CGFloat = 90.0
let topContentGradientRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: topGradientHeight))
transition.setPosition(view: self.topContentGradientView, position: topContentGradientRect.center)
transition.setBounds(view: self.topContentGradientView, bounds: CGRect(origin: CGPoint(), size: topContentGradientRect.size))
let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize)
var inputPanelAlpha: CGFloat = component.slice.peer.id == component.context.account.peerId || component.hideUI || self.isEditingStory ? 0.0 : 1.0
@ -2983,9 +3004,13 @@ public final class StoryItemSetContainerComponent: Component {
//transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0)
transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: 0.0)
var topGradientAlpha: CGFloat = (component.hideUI || self.displayViewList || self.isEditingStory) ? 0.0 : 1.0
var normalDimAlpha: CGFloat = 0.0
var forceDimAnimation = false
if let captionItem = self.captionItem {
if captionItem.externalState.isExpanded {
topGradientAlpha = 0.0
}
normalDimAlpha = captionItem.externalState.isExpanded ? 1.0 : 0.0
if transition.animation.isImmediate && transition.userData(StoryContentCaptionComponent.TransitionHint.self)?.kind == .isExpandedUpdated {
forceDimAnimation = true
@ -2998,6 +3023,12 @@ public final class StoryItemSetContainerComponent: Component {
transition.setFrame(view: self.contentDimView, frame: CGRect(origin: CGPoint(), size: contentFrame.size))
if transition.animation.isImmediate && forceDimAnimation && self.topContentGradientView.alpha != topGradientAlpha {
Transition(animation: .curve(duration: 0.25, curve: .easeInOut)).setAlpha(view: self.topContentGradientView, alpha: topGradientAlpha)
} else {
transition.setAlpha(view: self.topContentGradientView, alpha: topGradientAlpha)
}
if transition.animation.isImmediate && forceDimAnimation && self.contentDimView.alpha != dimAlpha {
Transition(animation: .curve(duration: 0.25, curve: .easeInOut)).setAlpha(view: self.contentDimView, alpha: dimAlpha)
} else {

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

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "smoothGradient.png",
"filename" : "smoothGradient 0.4.png",
"idiom" : "universal"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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),

View File

@ -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),

View File

@ -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()

View File

@ -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>?

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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):

View File

@ -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):