diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index a4b4776d43..2dbf113703 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1889,12 +1889,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.chatListDisplayNode.mainContainerNode.currentItemNode.updateState { chatListState in var chatListState = chatListState - var peerStoryMapping: [EnginePeer.Id: Bool] = [:] + var peerStoryMapping: [EnginePeer.Id: ChatListNodeState.StoryState] = [:] for item in rawStorySubscriptions.items { if item.peer.id == self.context.account.peerId { continue } - peerStoryMapping[item.peer.id] = item.hasUnseen + peerStoryMapping[item.peer.id] = ChatListNodeState.StoryState( + hasUnseen: item.hasUnseen, + hasUnseenCloseFriends: item.hasUnseenCloseFriends + ) } chatListState.peerStoryMapping = peerStoryMapping diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index aa9e89271c..b486e1b250 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -65,6 +65,19 @@ public enum ChatListItemContent { } } + public struct StoryState: Equatable { + public var hasUnseen: Bool + public var hasUnseenCloseFriends: Bool + + public init( + hasUnseen: Bool, + hasUnseenCloseFriends: Bool + ) { + self.hasUnseen = hasUnseen + self.hasUnseenCloseFriends = hasUnseenCloseFriends + } + } + public struct PeerData { public var messages: [EngineMessage] public var peer: EngineRenderedPeer @@ -83,7 +96,7 @@ public enum ChatListItemContent { public var forumTopicData: EngineChatList.ForumTopicData? public var topForumTopicItems: [EngineChatList.ForumTopicData] public var autoremoveTimeout: Int32? - public var storyState: Bool? + public var storyState: StoryState? public init( messages: [EngineMessage], @@ -103,7 +116,7 @@ public enum ChatListItemContent { forumTopicData: EngineChatList.ForumTopicData?, topForumTopicItems: [EngineChatList.ForumTopicData], autoremoveTimeout: Int32?, - storyState: Bool? + storyState: StoryState? ) { self.messages = messages self.peer = peer @@ -2747,9 +2760,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0) - var displayStoryIndicator: Bool? + var storyState: ChatListItemContent.StoryState? if case let .peer(peerData) = item.content { - displayStoryIndicator = peerData.storyState + storyState = peerData.storyState } let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) @@ -2764,7 +2777,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let storyIndicatorScale = avatarScale - if displayStoryIndicator != nil { + if storyState != nil { avatarScale *= (avatarFrame.width - 4.0 * 2.0) / avatarFrame.width } @@ -2775,7 +2788,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.avatarNode.updateSize(size: avatarFrame.size) strongSelf.updateVideoVisibility() - if let displayStoryIndicator { + if let storyState { var indicatorTransition = Transition(transition) let avatarStoryIndicator: ComponentView if let current = strongSelf.avatarStoryIndicator { @@ -2792,7 +2805,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let _ = avatarStoryIndicator.update( transition: indicatorTransition, component: AnyComponent(AvatarStoryIndicatorComponent( - hasUnseen: displayStoryIndicator, + hasUnseen: storyState.hasUnseen, + hasUnseenCloseFriendsItems: storyState.hasUnseenCloseFriends, isDarkTheme: item.presentationData.theme.overallDarkAppearance, activeLineWidth: 2.0, inactiveLineWidth: 1.0 + UIScreenPixel, diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 233e64a135..12b26eb8cd 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -218,6 +218,16 @@ private func areFoundPeerArraysEqual(_ lhs: [(EnginePeer, EnginePeer?)], _ rhs: } public struct ChatListNodeState: Equatable { + public struct StoryState: Equatable { + public var hasUnseen: Bool + public var hasUnseenCloseFriends: Bool + + public init(hasUnseen: Bool, hasUnseenCloseFriends: Bool) { + self.hasUnseen = hasUnseen + self.hasUnseenCloseFriends = hasUnseenCloseFriends + } + } + public struct ItemId: Hashable { public var peerId: EnginePeer.Id public var threadId: Int64? @@ -241,7 +251,7 @@ public struct ChatListNodeState: Equatable { public var foundPeers: [(EnginePeer, EnginePeer?)] public var selectedPeerMap: [EnginePeer.Id: EnginePeer] public var selectedThreadIds: Set - public var peerStoryMapping: [EnginePeer.Id: Bool] + public var peerStoryMapping: [EnginePeer.Id: ChatListNodeState.StoryState] public init( presentationData: ChatListPresentationData, @@ -257,7 +267,7 @@ public struct ChatListNodeState: Equatable { hiddenItemShouldBeTemporaryRevealed: Bool, hiddenPsaPeerId: EnginePeer.Id?, selectedThreadIds: Set, - peerStoryMapping: [EnginePeer.Id: Bool] + peerStoryMapping: [EnginePeer.Id: ChatListNodeState.StoryState] ) { self.presentationData = presentationData self.editing = editing @@ -396,7 +406,12 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL forumTopicData: forumTopicData, topForumTopicItems: topForumTopicItems, autoremoveTimeout: peerEntry.autoremoveTimeout, - storyState: peerEntry.storyState + storyState: peerEntry.storyState.flatMap { storyState in + return ChatListItemContent.StoryState( + hasUnseen: storyState.hasUnseen, + hasUnseenCloseFriends: storyState.hasUnseenCloseFriends + ) + } )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -742,7 +757,12 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL forumTopicData: forumTopicData, topForumTopicItems: topForumTopicItems, autoremoveTimeout: peerEntry.autoremoveTimeout, - storyState: peerEntry.storyState + storyState: peerEntry.storyState.flatMap { storyState in + return ChatListItemContent.StoryState( + hasUnseen: storyState.hasUnseen, + hasUnseenCloseFriends: storyState.hasUnseenCloseFriends + ) + } )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 1d90f7a111..0794bdf045 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -111,7 +111,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { var forumTopicData: EngineChatList.ForumTopicData? var topForumTopicItems: [EngineChatList.ForumTopicData] var revealed: Bool - var storyState: Bool? + var storyState: ChatListNodeState.StoryState? init( index: EngineChatList.Item.Index, @@ -136,7 +136,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { forumTopicData: EngineChatList.ForumTopicData?, topForumTopicItems: [EngineChatList.ForumTopicData], revealed: Bool, - storyState: Bool? + storyState: ChatListNodeState.StoryState? ) { self.index = index self.presentationData = presentationData diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 4f71ae67e6..32b073ccaa 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -82,6 +82,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { struct StoryData: Equatable { var count: Int var unseenCount: Int + var hasUnseenCloseFriends: Bool } case search(PresentationTheme, PresentationStrings) @@ -201,9 +202,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable { })] } - var storyStats: (total: Int, unseen: Int)? + var storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool)? if let storyData = storyData { - storyStats = (storyData.count, storyData.unseenCount) + storyStats = (storyData.count, storyData.unseenCount, storyData.hasUnseenCloseFriends) let text: String //TODO:localize @@ -534,7 +535,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text("HIDDEN STORIES", AnyHashable(0)), theme: theme, strings: strings) for item in storySubscriptions.items { - entries.append(.peer(index, .peer(peer: item.peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount))) + entries.append(.peer(index, .peer(peer: item.peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends))) index += 1 } } diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 64a66ee182..f7c799279c 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -180,7 +180,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { let arrowAction: (() -> Void)? let animationCache: AnimationCache? let animationRenderer: MultiAnimationRenderer? - let storyStats: (total: Int, unseen: Int)? + let storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool)? let openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? public let selectable: Bool @@ -217,7 +217,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil, animationCache: AnimationCache? = nil, animationRenderer: MultiAnimationRenderer? = nil, - storyStats: (total: Int, unseen: Int)? = nil, + storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool)? = nil, openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? = nil ) { self.presentationData = presentationData @@ -1114,6 +1114,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { transition: indicatorTransition, component: AnyComponent(AvatarStoryIndicatorComponent( hasUnseen: storyStats.unseen != 0, + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends, isDarkTheme: item.presentationData.theme.overallDarkAppearance, activeLineWidth: 1.0 + UIScreenPixel, inactiveLineWidth: 1.0 + UIScreenPixel, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 607ce89f69..3b3dc3833c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -124,6 +124,7 @@ public enum Stories { case isPinned case isExpired case isPublic + case isCloseFriends } public let id: Int32 @@ -137,6 +138,7 @@ public enum Stories { public let isPinned: Bool public let isExpired: Bool public let isPublic: Bool + public let isCloseFriends: Bool public init( id: Int32, @@ -149,7 +151,8 @@ public enum Stories { privacy: Privacy?, isPinned: Bool, isExpired: Bool, - isPublic: Bool + isPublic: Bool, + isCloseFriends: Bool ) { self.id = id self.timestamp = timestamp @@ -162,6 +165,7 @@ public enum Stories { self.isPinned = isPinned self.isExpired = isExpired self.isPublic = isPublic + self.isCloseFriends = isCloseFriends } public init(from decoder: Decoder) throws { @@ -184,6 +188,7 @@ public enum Stories { self.isPinned = try container.decodeIfPresent(Bool.self, forKey: .isPinned) ?? false self.isExpired = try container.decodeIfPresent(Bool.self, forKey: .isExpired) ?? false self.isPublic = try container.decodeIfPresent(Bool.self, forKey: .isPublic) ?? false + self.isCloseFriends = try container.decodeIfPresent(Bool.self, forKey: .isCloseFriends) ?? false } public func encode(to encoder: Encoder) throws { @@ -207,6 +212,7 @@ public enum Stories { try container.encode(self.isPinned, forKey: .isPinned) try container.encode(self.isExpired, forKey: .isExpired) try container.encode(self.isPublic, forKey: .isPublic) + try container.encode(self.isCloseFriends, forKey: .isCloseFriends) } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -251,6 +257,9 @@ public enum Stories { if lhs.isPublic != rhs.isPublic { return false } + if lhs.isCloseFriends != rhs.isCloseFriends { + return false + } return true } @@ -456,6 +465,7 @@ public final class EngineStorySubscriptions: Equatable { public final class Item: Equatable { public let peer: EnginePeer public let hasUnseen: Bool + public let hasUnseenCloseFriends: Bool public let storyCount: Int public let unseenCount: Int public let lastTimestamp: Int32 @@ -463,12 +473,14 @@ public final class EngineStorySubscriptions: Equatable { public init( peer: EnginePeer, hasUnseen: Bool, + hasUnseenCloseFriends: Bool, storyCount: Int, unseenCount: Int, lastTimestamp: Int32 ) { self.peer = peer self.hasUnseen = hasUnseen + self.hasUnseenCloseFriends = hasUnseenCloseFriends self.storyCount = storyCount self.unseenCount = unseenCount self.lastTimestamp = lastTimestamp @@ -484,6 +496,9 @@ public final class EngineStorySubscriptions: Equatable { if lhs.hasUnseen != rhs.hasUnseen { return false } + if lhs.hasUnseenCloseFriends != rhs.hasUnseenCloseFriends { + return false + } if lhs.storyCount != rhs.storyCount { return false } @@ -807,7 +822,8 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId privacy: Stories.Item.Privacy(base: privacy.base, additionallyIncludePeers: privacy.additionallyIncludePeers), isPinned: item.isPinned, isExpired: item.isExpired, - isPublic: item.isPublic + isPublic: item.isPublic, + isCloseFriends: item.isCloseFriends ) if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { items.append(StoryItemsTableEntry(value: entry, id: item.id)) @@ -954,7 +970,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor privacy: Stories.Item.Privacy(base: privacy.base, additionallyIncludePeers: privacy.additionallyIncludePeers), isPinned: item.isPinned, isExpired: item.isExpired, - isPublic: item.isPublic + isPublic: item.isPublic, + isCloseFriends: item.isCloseFriends ) if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { transaction.setStory(id: storyId, value: entry) @@ -975,7 +992,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor privacy: Stories.Item.Privacy(base: privacy.base, additionallyIncludePeers: privacy.additionallyIncludePeers), isPinned: item.isPinned, isExpired: item.isExpired, - isPublic: item.isPublic + isPublic: item.isPublic, + isCloseFriends: item.isCloseFriends ) if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { items[index] = StoryItemsTableEntry(value: entry, id: item.id) @@ -1105,7 +1123,8 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory privacy: item.privacy, isPinned: isPinned, isExpired: item.isExpired, - isPublic: item.isPublic + isPublic: item.isPublic, + isCloseFriends: item.isCloseFriends ) if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { items[index] = StoryItemsTableEntry(value: entry, id: item.id) @@ -1125,7 +1144,8 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory privacy: item.privacy, isPinned: isPinned, isExpired: item.isExpired, - isPublic: item.isPublic + isPublic: item.isPublic, + isCloseFriends: item.isCloseFriends ) updatedItems.append(updatedItem) } @@ -1220,6 +1240,7 @@ extension Stories.StoredItem { let isPinned = (flags & (1 << 5)) != 0 let isExpired = (flags & (1 << 6)) != 0 let isPublic = (flags & (1 << 7)) != 0 + let isCloseFriends = (flags & (1 << 8)) != 0 let item = Stories.Item( id: id, @@ -1232,7 +1253,8 @@ extension Stories.StoredItem { privacy: parsedPrivacy, isPinned: isPinned, isExpired: isExpired, - isPublic: isPublic + isPublic: isPublic, + isCloseFriends: isCloseFriends ) self = .item(item) } else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 3092e8752c..dcfad88ab4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -43,8 +43,9 @@ public final class EngineStoryItem: Equatable { public let isExpired: Bool public let isPublic: Bool public let isPending: Bool + public let isCloseFriends: Bool - public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool) { + public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool) { self.id = id self.timestamp = timestamp self.expirationTimestamp = expirationTimestamp @@ -57,6 +58,7 @@ public final class EngineStoryItem: Equatable { self.isExpired = isExpired self.isPublic = isPublic self.isPending = isPending + self.isCloseFriends = isCloseFriends } public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool { @@ -96,6 +98,9 @@ public final class EngineStoryItem: Equatable { if lhs.isPending != rhs.isPending { return false } + if lhs.isCloseFriends != rhs.isCloseFriends { + return false + } return true } } @@ -123,7 +128,8 @@ extension EngineStoryItem { }, isPinned: self.isPinned, isExpired: self.isExpired, - isPublic: self.isPublic + isPublic: self.isPublic, + isCloseFriends: self.isCloseFriends ) } } @@ -480,7 +486,8 @@ public final class PeerStoryListContext { isPinned: item.isPinned, isExpired: item.isExpired, isPublic: item.isPublic, - isPending: false + isPending: false, + isCloseFriends: item.isCloseFriends ) items.append(mappedItem) } @@ -585,7 +592,8 @@ public final class PeerStoryListContext { isPinned: item.isPinned, isExpired: item.isExpired, isPublic: item.isPublic, - isPending: false + isPending: false, + isCloseFriends: item.isCloseFriends ) storyItems.append(mappedItem) } @@ -717,7 +725,8 @@ public final class PeerStoryListContext { isPinned: item.isPinned, isExpired: item.isExpired, isPublic: item.isPublic, - isPending: false + isPending: false, + isCloseFriends: item.isCloseFriends ) finalUpdatedState = updatedState } @@ -754,7 +763,8 @@ public final class PeerStoryListContext { isPinned: item.isPinned, isExpired: item.isExpired, isPublic: item.isPublic, - isPending: false + isPending: false, + isCloseFriends: item.isCloseFriends )) updatedState.items.sort(by: { lhs, rhs in return lhs.timestamp > rhs.timestamp @@ -900,7 +910,8 @@ public final class PeerExpiringStoryListContext { isPinned: item.isPinned, isExpired: item.isExpired, isPublic: item.isPublic, - isPending: false + isPending: false, + isCloseFriends: item.isCloseFriends ) items.append(.item(mappedItem)) } @@ -1035,6 +1046,15 @@ public final class PeerExpiringStoryListContext { return timestamp } } + + public var isCloseFriends: Bool { + switch self { + case let .item(item): + return item.isCloseFriends + case .placeholder: + return false + } + } } public final class State: Equatable { @@ -1046,6 +1066,10 @@ public final class PeerExpiringStoryListContext { return self.items.contains(where: { $0.id > self.maxReadId }) } + public var hasUnseenCloseFriends: Bool { + return self.items.contains(where: { $0.id > self.maxReadId && $0.isCloseFriends }) + } + public init(items: [Item], isCached: Bool, maxReadId: Int32) { self.items = items self.isCached = isCached diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 04891900c0..33d644f41f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -679,6 +679,7 @@ public extension TelegramEngine { var accountItem: EngineStorySubscriptions.Item = EngineStorySubscriptions.Item( peer: EnginePeer(accountPeer), hasUnseen: false, + hasUnseenCloseFriends: false, storyCount: 0, unseenCount: 0, lastTimestamp: 0 @@ -693,6 +694,7 @@ public extension TelegramEngine { if let lastEntry = itemsView.items.last?.value.get(Stories.StoredItem.self) { let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) var hasUnseen = false + var hasUnseenCloseFriends = false var unseenCount = 0 if let peerState = peerState { hasUnseen = peerState.maxReadId < lastEntry.id @@ -700,6 +702,12 @@ public extension TelegramEngine { for item in itemsView.items { if item.id > peerState.maxReadId { unseenCount += 1 + + if case let .item(item) = item.value.get(Stories.StoredItem.self) { + if item.isCloseFriends { + hasUnseenCloseFriends = true + } + } } } } @@ -707,6 +715,7 @@ public extension TelegramEngine { let item = EngineStorySubscriptions.Item( peer: EnginePeer(accountPeer), hasUnseen: hasUnseen, + hasUnseenCloseFriends: hasUnseenCloseFriends, storyCount: itemsView.items.count, unseenCount: unseenCount, lastTimestamp: lastEntry.timestamp @@ -735,6 +744,7 @@ public extension TelegramEngine { let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) var hasUnseen = false + var hasUnseenCloseFriends = false var unseenCount = 0 if let peerState = peerState { hasUnseen = peerState.maxReadId < lastEntry.id @@ -742,6 +752,12 @@ public extension TelegramEngine { for item in itemsView.items { if item.id > peerState.maxReadId { unseenCount += 1 + + if case let .item(item) = item.value.get(Stories.StoredItem.self) { + if item.isCloseFriends { + hasUnseenCloseFriends = true + } + } } } } @@ -749,6 +765,7 @@ public extension TelegramEngine { let item = EngineStorySubscriptions.Item( peer: EnginePeer(peer), hasUnseen: hasUnseen, + hasUnseenCloseFriends: hasUnseenCloseFriends, storyCount: itemsView.items.count, unseenCount: unseenCount, lastTimestamp: lastEntry.timestamp @@ -935,7 +952,8 @@ public extension TelegramEngine { privacy: item.privacy, isPinned: item.isPinned, isExpired: item.isExpired, - isPublic: item.isPublic + isPublic: item.isPublic, + isCloseFriends: item.isCloseFriends )) if let entry = CodableEntry(updatedItem) { currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id) diff --git a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift index 048295307d..030abd5b99 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift @@ -30,7 +30,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { private var avatarVideoNode: AvatarVideoNode? public private(set) var avatarStoryView: ComponentView? - public var hasUnseenStories: Bool? + public var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool)? public let statusView: ComponentView @@ -78,7 +78,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { self.avatarNode.frame = self.containerNode.bounds #if DEBUG - self.hasUnseenStories = true + //self.hasUnseenStories = true #endif } @@ -200,7 +200,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { } public func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme) { - if let hasUnseenStories = self.hasUnseenStories { + if let storyData = self.storyData { let avatarStoryView: ComponentView if let current = self.avatarStoryView { avatarStoryView = current @@ -212,7 +212,8 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { let _ = avatarStoryView.update( transition: Transition(transition), component: AnyComponent(AvatarStoryIndicatorComponent( - hasUnseen: hasUnseenStories, + hasUnseen: storyData.hasUnseen, + hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends, isDarkTheme: theme.overallDarkAppearance, activeLineWidth: 1.0, inactiveLineWidth: 1.0, diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index fd5b09f3f4..08ed7b98ea 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -16,6 +16,7 @@ public final class AvatarStoryIndicatorComponent: Component { } public let hasUnseen: Bool + public let hasUnseenCloseFriendsItems: Bool public let isDarkTheme: Bool public let activeLineWidth: CGFloat public let inactiveLineWidth: CGFloat @@ -23,12 +24,14 @@ public final class AvatarStoryIndicatorComponent: Component { public init( hasUnseen: Bool, + hasUnseenCloseFriendsItems: Bool, isDarkTheme: Bool, activeLineWidth: CGFloat, inactiveLineWidth: CGFloat, counters: Counters? ) { self.hasUnseen = hasUnseen + self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems self.isDarkTheme = isDarkTheme self.activeLineWidth = activeLineWidth self.inactiveLineWidth = inactiveLineWidth @@ -39,6 +42,9 @@ public final class AvatarStoryIndicatorComponent: Component { if lhs.hasUnseen != rhs.hasUnseen { return false } + if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems { + return false + } if lhs.isDarkTheme != rhs.isDarkTheme { return false } @@ -91,6 +97,29 @@ public final class AvatarStoryIndicatorComponent: Component { self.indicatorView.image = generateImage(CGSize(width: imageDiameter, height: imageDiameter), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) + let activeColors: [CGColor] + let inactiveColors: [CGColor] + + if component.hasUnseenCloseFriendsItems { + activeColors = [ + UIColor(rgb: 0x7CD636).cgColor, + UIColor(rgb: 0x26B470).cgColor + ] + } else { + activeColors = [ + UIColor(rgb: 0x34C76F).cgColor, + UIColor(rgb: 0x3DA1FD).cgColor + ] + } + + if component.isDarkTheme { + inactiveColors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor] + } else { + inactiveColors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor] + } + + var locations: [CGFloat] = [0.0, 1.0] + context.setLineWidth(lineWidth) if let counters = component.counters, counters.totalCount > 1 { @@ -124,16 +153,11 @@ public final class AvatarStoryIndicatorComponent: Component { context.replacePathWithStrokedPath() context.clip() - var locations: [CGFloat] = [1.0, 0.0] let colors: [CGColor] if pass == 1 { - colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor] + colors = activeColors } else { - if component.isDarkTheme { - colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor] - } else { - colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor] - } + colors = inactiveColors } let colorSpace = CGColorSpaceCreateDeviceRGB() @@ -148,16 +172,11 @@ public final class AvatarStoryIndicatorComponent: Component { context.replacePathWithStrokedPath() context.clip() - var locations: [CGFloat] = [1.0, 0.0] let colors: [CGColor] if component.hasUnseen { - colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor] + colors = activeColors } else { - if component.isDarkTheme { - colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor] - } else { - colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor] - } + colors = inactiveColors } let colorSpace = CGColorSpaceCreateDeviceRGB() diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift index 165db2a6a8..76f0ca8379 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift @@ -138,7 +138,8 @@ public final class StoryContentContextImpl: StoryContentContext { isPinned: item.isPinned, isExpired: item.isExpired, isPublic: item.isPublic, - isPending: false + isPending: false, + isCloseFriends: item.isCloseFriends ) } if peerId == context.account.peerId, let stateView = views.views[PostboxViewKey.storiesState(key: .local)] as? StoryStatesView, let localState = stateView.value?.get(Stories.LocalState.self) { @@ -155,7 +156,8 @@ public final class StoryContentContextImpl: StoryContentContext { isPinned: item.pin, isExpired: false, isPublic: false, - isPending: true + isPending: true, + isCloseFriends: false )) } } @@ -434,6 +436,7 @@ public final class StoryContentContextImpl: StoryContentContext { items: [EngineStorySubscriptions.Item( peer: peer, hasUnseen: state.hasUnseen, + hasUnseenCloseFriends: state.hasUnseenCloseFriends, storyCount: state.items.count, unseenCount: 0, lastTimestamp: state.items.last?.timestamp ?? 0 @@ -953,7 +956,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext { isPinned: itemValue.isPinned, isExpired: itemValue.isExpired, isPublic: itemValue.isPublic, - isPending: false + isPending: false, + isCloseFriends: itemValue.isCloseFriends ) let stateValue = StoryContentContextState( diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 45c84e0515..8087785735 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -603,6 +603,8 @@ public final class StoryPeerListComponent: Component { var hasUnseen = false hasUnseen = itemSet.hasUnseen + var hasUnseenCloseFriendsItems = itemSet.hasUnseenCloseFriends + var hasItems = true var itemRingAnimation: StoryPeerListItemComponent.RingAnimation? if peer.id == component.context.account.peerId { @@ -614,6 +616,8 @@ public final class StoryPeerListComponent: Component { if let uploadProgress = component.uploadProgress { itemRingAnimation = .progress(uploadProgress) } + + hasUnseenCloseFriendsItems = false } let measuredItem = calculateItem(i) @@ -655,6 +659,7 @@ public final class StoryPeerListComponent: Component { strings: component.strings, peer: peer, hasUnseen: hasUnseen, + hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems, hasItems: hasItems, ringAnimation: itemRingAnimation, collapseFraction: isReallyVisible ? (1.0 - collapsedState.maxFraction) : 0.0, diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index 29200a3680..40b0b1fb7a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -266,6 +266,7 @@ public final class StoryPeerListItemComponent: Component { public let strings: PresentationStrings public let peer: EnginePeer public let hasUnseen: Bool + public let hasUnseenCloseFriendsItems: Bool public let hasItems: Bool public let ringAnimation: RingAnimation? public let collapseFraction: CGFloat @@ -283,6 +284,7 @@ public final class StoryPeerListItemComponent: Component { strings: PresentationStrings, peer: EnginePeer, hasUnseen: Bool, + hasUnseenCloseFriendsItems: Bool, hasItems: Bool, ringAnimation: RingAnimation?, collapseFraction: CGFloat, @@ -299,6 +301,7 @@ public final class StoryPeerListItemComponent: Component { self.strings = strings self.peer = peer self.hasUnseen = hasUnseen + self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems self.hasItems = hasItems self.ringAnimation = ringAnimation self.collapseFraction = collapseFraction @@ -327,6 +330,9 @@ public final class StoryPeerListItemComponent: Component { if lhs.hasUnseen != rhs.hasUnseen { return false } + if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems { + return false + } if lhs.hasItems != rhs.hasItems { return false } @@ -646,7 +652,17 @@ public final class StoryPeerListItemComponent: Component { let colors: [CGColor] if component.hasUnseen || component.ringAnimation != nil { - colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor] + if component.hasUnseenCloseFriendsItems { + colors = [ + UIColor(rgb: 0x7CD636).cgColor, + UIColor(rgb: 0x26B470).cgColor + ] + } else { + colors = [ + UIColor(rgb: 0x34C76F).cgColor, + UIColor(rgb: 0x3DA1FD).cgColor + ] + } } else { if component.theme.overallDarkAppearance { colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor] diff --git a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift index df784c454e..f4e01a6351 100644 --- a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift @@ -71,13 +71,13 @@ private final class ShapeImageView: UIView { if let image = item.image { context.translateBy(x: imageRect.midX, y: imageRect.midY) - context.scaleBy(x: 1.0, y: -1.0) + context.scaleBy(x: 1.0, y: 1.0) context.translateBy(x: -imageRect.midX, y: -imageRect.midY) image.draw(in: imageRect, blendMode: .normal, alpha: 1.0) context.translateBy(x: imageRect.midX, y: imageRect.midY) - context.scaleBy(x: 1.0, y: -1.0) + context.scaleBy(x: 1.0, y: 1.0) context.translateBy(x: -imageRect.midX, y: -imageRect.midY) } else { context.setFillColor(UIColor.black.cgColor) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index d6891017ad..d334e398c7 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -421,7 +421,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { private let playbackStartDisposable = MetaDisposable() - var hasUnseenStories: Bool? + var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool)? init(context: AccountContext) { self.context = context @@ -455,7 +455,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme) { - if let hasUnseenStories = self.hasUnseenStories { + if let storyData = self.storyData { let avatarStoryView: ComponentView if let current = self.avatarStoryView { avatarStoryView = current @@ -467,7 +467,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { let _ = avatarStoryView.update( transition: Transition(transition), component: AnyComponent(AvatarStoryIndicatorComponent( - hasUnseen: hasUnseenStories, + hasUnseen: storyData.hasUnseen, + hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends, isDarkTheme: theme.overallDarkAppearance, activeLineWidth: 3.0, inactiveLineWidth: 2.0, diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 383eb14dfc..f14f81d909 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3879,10 +3879,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } self.expiringStoryListState = state if state.items.isEmpty { - self.headerNode.avatarListNode.avatarContainerNode.hasUnseenStories = nil + self.headerNode.avatarListNode.avatarContainerNode.storyData = nil self.headerNode.avatarListNode.listContainerNode.storyParams = nil } else { - self.headerNode.avatarListNode.avatarContainerNode.hasUnseenStories = state.hasUnseen + self.headerNode.avatarListNode.avatarContainerNode.storyData = (state.hasUnseen, state.hasUnseenCloseFriends) self.headerNode.avatarListNode.listContainerNode.storyParams = (peer, state.items.prefix(3).compactMap { item -> EngineStoryItem? in switch item { case let .item(item):