This commit is contained in:
Ali 2023-08-01 19:57:08 +03:00
parent 5665d89419
commit ae49ad3700
29 changed files with 805 additions and 149 deletions

View File

@ -9747,3 +9747,6 @@ Sorry for the inconvenience.";
"Story.Privacy.HideMyStoriesFrom" = "Hide My Stories From";
"Story.Privacy.SaveList" = "Save List";
"StoryList.TooltipStoriesSavedToProfile_1" = "Story archived";
"StoryList.TooltipStoriesSavedToProfile_any" = "%d stories archived.";

View File

@ -315,23 +315,23 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
}
case topic(EnginePeer, ChatListItemContent.ThreadInfo, Int, PresentationTheme, PresentationStrings, ChatListSearchSectionExpandType)
case recentlySearchedPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)
case localPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, EngineMessageHistoryThread.Info?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int64, isFirstInList: Bool)?, MessageSection, Bool)
case recentlySearchedPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, PeerStoryStats?)
case localPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType, PeerStoryStats?)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType, PeerStoryStats?)
case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, EngineMessageHistoryThread.Info?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int64, isFirstInList: Bool)?, MessageSection, Bool, PeerStoryStats?)
case addContact(String, PresentationTheme, PresentationStrings)
public var stableId: ChatListSearchEntryStableId {
switch self {
case let .topic(_, threadInfo, _, _, _, _):
return .threadId(threadInfo.id)
case let .recentlySearchedPeer(peer, _, _, _, _, _, _, _):
case let .recentlySearchedPeer(peer, _, _, _, _, _, _, _, _):
return .localPeerId(peer.id)
case let .localPeer(peer, _, _, _, _, _, _, _, _):
case let .localPeer(peer, _, _, _, _, _, _, _, _, _):
return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _):
case let .globalPeer(peer, _, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id)
case let .message(message, _, _, _, _, _, _, _, _, _, section, _):
case let .message(message, _, _, _, _, _, _, _, _, _, section, _, _):
return .messageId(message.id, section)
case .addContact:
return .addContact
@ -346,26 +346,26 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} else {
return false
}
case let .recentlySearchedPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder):
if case let .recentlySearchedPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 {
case let .recentlySearchedPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsStoryStats):
if case let .recentlySearchedPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsStoryStats) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsStoryStats == rhsStoryStats {
return true
} else {
return false
}
case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType, lhsStoryStats):
if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType, rhsStoryStats) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType && lhsStoryStats == rhsStoryStats {
return true
} else {
return false
}
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType, lhsStoryStats):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType, rhsStoryStats) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType && lhsStoryStats == rhsStoryStats {
return true
} else {
return false
}
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsThreadInfo, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader, lhsKey, lhsResourceId, lhsSection, lhsAllPaused):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsThreadInfo, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader, rhsKey, rhsResourceId, rhsSection, rhsAllPaused) = rhs {
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsThreadInfo, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader, lhsKey, lhsResourceId, lhsSection, lhsAllPaused, lhsStoryStats):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsThreadInfo, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader, rhsKey, rhsResourceId, rhsSection, rhsAllPaused, rhsStoryStats) = rhs {
if lhsMessage.id != rhsMessage.id {
return false
}
@ -408,6 +408,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
if lhsAllPaused != rhsAllPaused {
return false
}
if lhsStoryStats != rhsStoryStats {
return false
}
return true
} else {
return false
@ -438,34 +441,34 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} else {
return true
}
case let .recentlySearchedPeer(_, _, _, lhsIndex, _, _, _, _):
case let .recentlySearchedPeer(_, _, _, lhsIndex, _, _, _, _, _):
if case .topic = rhs {
return false
} else if case let .recentlySearchedPeer(_, _, _, rhsIndex, _, _, _, _) = rhs {
} else if case let .recentlySearchedPeer(_, _, _, rhsIndex, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else {
return true
}
case let .localPeer(_, _, _, lhsIndex, _, _, _, _, _):
case let .localPeer(_, _, _, lhsIndex, _, _, _, _, _, _):
switch rhs {
case .topic, .recentlySearchedPeer:
return false
case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _):
case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .globalPeer, .message, .addContact:
return true
}
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _):
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _, _):
switch rhs {
case .topic, .recentlySearchedPeer, .localPeer:
return false
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _):
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .message, .addContact:
return true
}
case let .message(_, _, _, _, _, _, _, _, lhsKey, _, _, _):
if case let .message(_, _, _, _, _, _, _, _, rhsKey, _, _, _) = rhs {
case let .message(_, _, _, _, _, _, _, _, lhsKey, _, _, _, _):
if case let .message(_, _, _, _, _, _, _, _, rhsKey, _, _, _, _) = rhs {
return lhsKey < rhsKey
} else if case .addContact = rhs {
return true
@ -496,7 +499,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .generalSearch, peer: .thread(peer: peer, title: threadInfo.info.title, icon: threadInfo.info.icon, color: threadInfo.info.iconColor), status: .none, badge: nil, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
interaction.peerSelected(peer, nil, threadInfo.id, nil)
}, contextAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
case let .recentlySearchedPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder):
case let .recentlySearchedPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, storyStats):
let primaryPeer: EnginePeer
var chatPeer: EnginePeer?
if let associatedPeer = associatedPeer {
@ -571,8 +574,10 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
gesture?.cancel()
}
}
}, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
}, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in
return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends)
})
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, storyStats):
let primaryPeer: EnginePeer
var chatPeer: EnginePeer?
if let associatedPeer = associatedPeer {
@ -662,8 +667,10 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
gesture?.cancel()
}
}
}, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
}, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in
return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends)
})
case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, storyStats):
var enabled = true
if filter.contains(.onlyWriteable) {
enabled = canSendMessagesToPeer(peer.peer)
@ -721,8 +728,10 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
return { node, gesture, location in
peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location)
}
}, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
case let .message(message, peer, readState, threadInfo, presentationData, _, selected, displayCustomHeader, orderingKey, _, _, allPaused):
}, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in
return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends)
})
case let .message(message, peer, readState, threadInfo, presentationData, _, selected, displayCustomHeader, orderingKey, _, _, allPaused, storyStats):
let header: ChatListSearchItemHeader
switch orderingKey {
case .downloading:
@ -798,7 +807,16 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
forumTopicData: nil,
topForumTopicItems: [],
autoremoveTimeout: nil,
storyState: nil
storyState: storyStats.flatMap { stats in
return ChatListItemContent.StoryState(
stats: EngineChatList.StoryStats(
totalCount: stats.totalCount,
unseenCount: stats.unseenCount,
hasUnseenCloseFriends: stats.hasUnseenCloseFriends
),
hasUnseenCloseFriends: stats.hasUnseenCloseFriends
)
}
)), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
}
case let .addContact(phoneNumber, theme, strings):
@ -1317,7 +1335,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
resource = (resourceValue.id.stringRepresentation, size, entries.isEmpty)
}
entries.append(.message(message, peer, nil, nil, presentationData, 1, nil, false, .downloading(item.priority), resource, .downloading, allPaused))
entries.append(.message(message, peer, nil, nil, presentationData, 1, nil, false, .downloading(item.priority), resource, .downloading, allPaused, nil))
}
for item in downloadItems.doneItems.sorted(by: { ChatListSearchEntry.MessageOrderingKey.downloaded(timestamp: $0.timestamp, index: $0.message.index) < ChatListSearchEntry.MessageOrderingKey.downloaded(timestamp: $1.timestamp, index: $1.message.index) }) {
if !item.isSeen {
@ -1345,7 +1363,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
}
entries.append(.message(message, peer, nil, nil, presentationData, 1, selectionState?.contains(message.id), false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false))
entries.append(.message(message, peer, nil, nil, presentationData, 1, selectionState?.contains(message.id), false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false, nil))
}
return (entries.sorted(), false)
}
@ -1875,7 +1893,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if lowercasedQuery.count > 1 && (presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery)) {
if !existingPeerIds.contains(accountPeer.id), filteredPeer(EnginePeer(accountPeer), EnginePeer(accountPeer)) {
existingPeerIds.insert(accountPeer.id)
entries.append(.localPeer(EnginePeer(accountPeer), nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
entries.append(.localPeer(EnginePeer(accountPeer), nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil))
index += 1
}
}
@ -1893,7 +1911,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
associatedPeer = renderedPeer.peers[associatedPeerId]
}
entries.append(.recentlySearchedPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder))
entries.append(.recentlySearchedPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, nil))
index += 1
}
@ -1918,7 +1936,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if matches {
existingPeerIds.insert(peer.id)
entries.append(.localPeer(peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
entries.append(.localPeer(peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil))
}
}
}
@ -1941,7 +1959,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
associatedPeer = renderedPeer.peers[associatedPeerId]
}
entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil))
index += 1
numberOfLocalPeers += 1
}
@ -1955,7 +1973,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) {
existingPeerIds.insert(peer.peer.id)
entries.append(.localPeer(EnginePeer(peer.peer), nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
entries.append(.localPeer(EnginePeer(peer.peer), nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil))
index += 1
numberOfLocalPeers += 1
}
@ -1972,7 +1990,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) {
existingPeerIds.insert(peer.peer.id)
entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalExpandType))
entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalExpandType, nil))
index += 1
numberOfGlobalPeers += 1
}
@ -1986,7 +2004,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
peer = EngineRenderedPeer(peer: EnginePeer(channelPeer))
}
}
entries.append(.message(message, peer, nil, nil, presentationData, 1, nil, true, .index(message.index), nil, .generic, false))
entries.append(.message(message, peer, nil, nil, presentationData, 1, nil, true, .index(message.index), nil, .generic, false, nil))
index += 1
}
@ -2017,7 +2035,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
}
entries.append(.message(message, peer, foundRemoteMessageSet.readCounters[message.id.peerId], foundRemoteMessageSet.threadsData[message.id]?.info, presentationData, foundRemoteMessageSet.totalCount, selectionState?.contains(message.id), headerId == firstHeaderId, .index(message.index), nil, .generic, false))
entries.append(.message(message, peer, foundRemoteMessageSet.readCounters[message.id.peerId], foundRemoteMessageSet.threadsData[message.id]?.info, presentationData, foundRemoteMessageSet.totalCount, selectionState?.contains(message.id), headerId == firstHeaderId, .index(message.index), nil, .generic, false, nil))
index += 1
}
}
@ -2232,7 +2250,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var fetchResourceId: (id: String, size: Int64, isFirstInList: Bool)?
for entry in currentEntries {
switch entry {
case let .message(m, _, _, _, _, _, _, _, _, resource, _, _):
case let .message(m, _, _, _, _, _, _, _, _, resource, _, _, _):
if m.id == message.id {
fetchResourceId = resource
}
@ -2298,7 +2316,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if let entries = entriesAndFlags {
var filteredEntries: [ChatListSearchEntry] = []
for entry in entries {
if case let .localPeer(peer, _, _, _, _, _, _, _, _) = entry {
if case let .localPeer(peer, _, _, _, _, _, _, _, _, _) = entry {
peers.append(peer)
} else if case .globalPeer = entry {
} else {
@ -2411,7 +2429,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var messages: [EngineMessage] = []
for entry in newEntries {
if case let .message(message, _, _, _, _, _, _, _, _, _, _, _) = entry {
if case let .message(message, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
messages.append(message)
}
}

View File

@ -702,7 +702,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
var index: UInt32 = 0
if let entries = entries {
for entry in entries {
if case let .message(message, _, _, _, _, _, _, _, _, _, _, _) = entry {
if case let .message(message, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
self.mediaItems.append(VisualMediaItem(message: message._asMessage(), index: nil))
}
index += 1

View File

@ -79,7 +79,7 @@ public func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat,
if useModernPathCalculation {
if rects.count == 1 {
let path = UIBezierPath(roundedRect: rects[0], cornerRadius: outerRadius).cgPath
let path = UIBezierPath(roundedRect: rects[0].offsetBy(dx: -topLeft.x, dy: -topLeft.y), cornerRadius: outerRadius).cgPath
context.addPath(path)
if stroke {

View File

@ -55,7 +55,7 @@ public final class HashtagSearchController: TelegramBaseController {
|> map { result, presentationData in
let result = result.0
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
return result.messages.map({ .message(EngineMessage($0), EngineRenderedPeer(message: EngineMessage($0)), result.readStates[$0.id.peerId].flatMap { EnginePeerReadCounters(state: $0, isMuted: false) }, nil, chatListPresentationData, result.totalCount, nil, false, .index($0.index), nil, .generic, false) })
return result.messages.map({ .message(EngineMessage($0), EngineRenderedPeer(message: EngineMessage($0)), result.readStates[$0.id.peerId].flatMap { EnginePeerReadCounters(state: $0, isMuted: false) }, nil, chatListPresentationData, result.totalCount, nil, false, .index($0.index), nil, .generic, false, nil) })
}
let interaction = ChatListNodeInteraction(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {
}, peerSelected: { _, _, _, _ in

View File

@ -2391,13 +2391,13 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
self.isUserInteractionEnabled = false
}
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, animationCache: AnimationCache, reaction: ReactionItem, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, forceSmallEffectAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
self.animateReactionSelection(context: context, theme: theme, animationCache: animationCache, reaction: reaction, avatarPeers: avatarPeers, playHaptic: playHaptic, isLarge: isLarge, forceSmallEffectAnimation: forceSmallEffectAnimation, targetView: targetView, addStandaloneReactionAnimation: addStandaloneReactionAnimation, currentItemNode: nil, completion: completion)
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, animationCache: AnimationCache, reaction: ReactionItem, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, forceSmallEffectAnimation: Bool = false, hideCenterAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
self.animateReactionSelection(context: context, theme: theme, animationCache: animationCache, reaction: reaction, avatarPeers: avatarPeers, playHaptic: playHaptic, isLarge: isLarge, forceSmallEffectAnimation: forceSmallEffectAnimation, hideCenterAnimation: hideCenterAnimation, targetView: targetView, addStandaloneReactionAnimation: addStandaloneReactionAnimation, currentItemNode: nil, completion: completion)
}
public var currentDismissAnimation: (() -> Void)?
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, animationCache: AnimationCache, reaction: ReactionItem, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, forceSmallEffectAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, currentItemNode: ReactionNode?, completion: @escaping () -> Void) {
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, animationCache: AnimationCache, reaction: ReactionItem, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, forceSmallEffectAnimation: Bool = false, hideCenterAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, currentItemNode: ReactionNode?, completion: @escaping () -> Void) {
guard let sourceSnapshotView = targetView.snapshotContentTree() else {
completion()
return
@ -2430,7 +2430,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
switchToInlineImmediately = false
}
if !forceSmallEffectAnimation && !switchToInlineImmediately {
if !forceSmallEffectAnimation && !switchToInlineImmediately && !hideCenterAnimation {
if let targetView = targetView as? ReactionIconView, !isLarge {
self.itemNodeIsEmbedded = true
targetView.addSubnode(itemNode)

View File

@ -137,6 +137,7 @@ public enum Stories {
case isSelectedContacts
case isForwardingDisabled
case isEdited
case hasLike
}
public let id: Int32
@ -156,6 +157,7 @@ public enum Stories {
public let isSelectedContacts: Bool
public let isForwardingDisabled: Bool
public let isEdited: Bool
public let hasLike: Bool
public init(
id: Int32,
@ -174,7 +176,8 @@ public enum Stories {
isContacts: Bool,
isSelectedContacts: Bool,
isForwardingDisabled: Bool,
isEdited: Bool
isEdited: Bool,
hasLike: Bool
) {
self.id = id
self.timestamp = timestamp
@ -193,6 +196,7 @@ public enum Stories {
self.isSelectedContacts = isSelectedContacts
self.isForwardingDisabled = isForwardingDisabled
self.isEdited = isEdited
self.hasLike = hasLike
}
public init(from decoder: Decoder) throws {
@ -221,6 +225,7 @@ public enum Stories {
self.isSelectedContacts = try container.decodeIfPresent(Bool.self, forKey: .isSelectedContacts) ?? false
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false
self.isEdited = try container.decodeIfPresent(Bool.self, forKey: .isEdited) ?? false
self.hasLike = try container.decodeIfPresent(Bool.self, forKey: .hasLike) ?? false
}
public func encode(to encoder: Encoder) throws {
@ -250,6 +255,7 @@ public enum Stories {
try container.encode(self.isSelectedContacts, forKey: .isSelectedContacts)
try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled)
try container.encode(self.isEdited, forKey: .isEdited)
try container.encode(self.hasLike, forKey: .hasLike)
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
@ -311,6 +317,9 @@ public enum Stories {
if lhs.isEdited != rhs.isEdited {
return false
}
if lhs.hasLike != rhs.hasLike {
return false
}
return true
}
@ -959,7 +968,8 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends))
@ -1122,7 +1132,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
transaction.setStory(id: storyId, value: entry)
@ -1149,7 +1160,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
@ -1276,7 +1288,8 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
@ -1302,7 +1315,8 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
updatedItems.append(updatedItem)
}
@ -1420,7 +1434,8 @@ extension Stories.StoredItem {
isContacts: isContacts,
isSelectedContacts: isSelectedContacts,
isForwardingDisabled: isForwardingDisabled,
isEdited: isEdited
isEdited: isEdited,
hasLike: false
)
self = .item(item)
} else {
@ -1585,15 +1600,18 @@ public final class EngineStoryViewListContext {
public let peer: EnginePeer
public let timestamp: Int32
public let storyStats: PeerStoryStats?
public let isLike: Bool
public init(
peer: EnginePeer,
timestamp: Int32,
storyStats: PeerStoryStats?
storyStats: PeerStoryStats?,
isLike: Bool
) {
self.peer = peer
self.timestamp = timestamp
self.storyStats = storyStats
self.isLike = isLike
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
@ -1606,6 +1624,9 @@ public final class EngineStoryViewListContext {
if lhs.storyStats != rhs.storyStats {
return false
}
if lhs.isLike != rhs.isLike {
return false
}
return true
}
}
@ -1726,7 +1747,7 @@ public final class EngineStoryViewListContext {
return previousData.withUpdatedIsBlocked(isBlocked).withUpdatedFlags(updatedFlags)
})
if let peer = transaction.getPeer(peerId) {
items.append(Item(peer: EnginePeer(peer), timestamp: date, storyStats: transaction.getPeerStoryStats(peerId: peerId)))
items.append(Item(peer: EnginePeer(peer), timestamp: date, storyStats: transaction.getPeerStoryStats(peerId: peerId), isLike: false))
nextOffset = NextOffset(id: userId, timestamp: date)
}
@ -1751,7 +1772,8 @@ public final class EngineStoryViewListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry)
@ -1779,7 +1801,8 @@ public final class EngineStoryViewListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
@ -1849,7 +1872,8 @@ public final class EngineStoryViewListContext {
items[i] = Item(
peer: item.peer,
timestamp: item.timestamp,
storyStats: value
storyStats: value,
isLike: false
)
}
}
@ -2122,3 +2146,66 @@ public func _internal_setStoryNotificationWasDisplayed(transaction: Transaction,
key.setInt32(8, value: id.id)
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.displayedStoryNotifications, key: key), entry: CodableEntry(data: Data()))
}
func _internal_setStoryLike(account: Account, peerId: EnginePeer.Id, id: Int32, hasLike: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Void in
var currentItems = transaction.getStoryItems(peerId: peerId)
for i in 0 ..< currentItems.count {
if currentItems[i].id == id {
if case let .item(item) = currentItems[i].value.get(Stories.StoredItem.self) {
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: item.media,
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views,
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isEdited,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
hasLike: hasLike
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
}
}
}
}
transaction.setStoryItems(peerId: peerId, items: currentItems)
if let current = transaction.getStory(id: StoryId(peerId: peerId, id: id))?.get(Stories.StoredItem.self), case let .item(item) = current {
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: item.media,
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views,
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isEdited,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
hasLike: hasLike
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: peerId, id: id), value: entry)
}
}
}
|> ignoreValues
}

View File

@ -49,8 +49,9 @@ public final class EngineStoryItem: Equatable {
public let isSelectedContacts: Bool
public let isForwardingDisabled: Bool
public let isEdited: Bool
public let hasLike: Bool
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool) {
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, hasLike: Bool) {
self.id = id
self.timestamp = timestamp
self.expirationTimestamp = expirationTimestamp
@ -69,6 +70,7 @@ public final class EngineStoryItem: Equatable {
self.isSelectedContacts = isSelectedContacts
self.isForwardingDisabled = isForwardingDisabled
self.isEdited = isEdited
self.hasLike = hasLike
}
public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool {
@ -126,6 +128,9 @@ public final class EngineStoryItem: Equatable {
if lhs.isEdited != rhs.isEdited {
return false
}
if lhs.hasLike != rhs.hasLike {
return false
}
return true
}
}
@ -159,7 +164,8 @@ extension EngineStoryItem {
isContacts: self.isContacts,
isSelectedContacts: self.isSelectedContacts,
isForwardingDisabled: self.isForwardingDisabled,
isEdited: self.isEdited
isEdited: self.isEdited,
hasLike: self.hasLike
)
}
}
@ -528,7 +534,8 @@ public final class PeerStoryListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
items.append(mappedItem)
@ -653,7 +660,8 @@ public final class PeerStoryListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
storyItems.append(mappedItem)
}
@ -802,7 +810,8 @@ public final class PeerStoryListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
finalUpdatedState = updatedState
}
@ -842,7 +851,8 @@ public final class PeerStoryListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
finalUpdatedState = updatedState
} else {
@ -884,7 +894,8 @@ public final class PeerStoryListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
))
updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp
@ -922,7 +933,8 @@ public final class PeerStoryListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
))
updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp
@ -1084,7 +1096,8 @@ public final class PeerExpiringStoryListContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
items.append(.item(mappedItem))
}

View File

@ -1034,7 +1034,8 @@ public extension TelegramEngine {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
@ -1109,5 +1110,9 @@ public extension TelegramEngine {
public func enableStoryStealthMode() -> Signal<Never, NoError> {
return _internal_enableStoryStealthMode(account: self.account)
}
public func setStoryLike(peerId: EnginePeer.Id, id: Int32, hasLike: Bool) -> Signal<Never, NoError> {
return _internal_setStoryLike(account: self.account, peerId: peerId, id: id, hasLike: hasLike)
}
}
}

View File

@ -295,6 +295,8 @@ public enum PresentationResourceKey: Int32 {
case chatGeneralThreadFreeIcon
case uploadToneIcon
case storyViewListLikeIcon
}
public enum ChatExpiredStoryIndicatorType: Hashable {

View File

@ -1298,4 +1298,10 @@ public struct PresentationResourcesChat {
})
})
}
public static func storyViewListLikeIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.storyViewListLikeIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputLikeOn"), color: UIColor(rgb: 0xFF3B30))
})
}
}

View File

@ -1113,6 +1113,8 @@ final class MediaEditorScreenComponent: Component {
stopAndPreviewMediaRecording: nil,
discardMediaRecordingPreview: nil,
attachmentAction: nil,
hasLike: false,
likeAction: nil,
inputModeAction: { [weak self] in
if let self {
switch self.currentInputMode {

View File

@ -266,6 +266,8 @@ final class StoryPreviewComponent: Component {
stopAndPreviewMediaRecording: nil,
discardMediaRecordingPreview: nil,
attachmentAction: { },
hasLike: false,
likeAction: nil,
inputModeAction: nil,
timeoutAction: nil,
forwardAction: {},

View File

@ -19,6 +19,12 @@ private extension MessageInputActionButtonComponent.Mode {
return "Chat/Input/Text/IconAttachment"
case .forward:
return "Chat/Input/Text/IconForwardSend"
case let .like(isActive):
if isActive {
return "Stories/InputLikeOn"
} else {
return "Stories/InputLikeOff"
}
default:
return nil
}
@ -26,7 +32,7 @@ private extension MessageInputActionButtonComponent.Mode {
}
public final class MessageInputActionButtonComponent: Component {
public enum Mode {
public enum Mode: Equatable {
case none
case send
case apply
@ -37,6 +43,7 @@ public final class MessageInputActionButtonComponent: Component {
case attach
case forward
case more
case like(isActive: Bool)
}
public enum Action {
@ -299,7 +306,7 @@ public final class MessageInputActionButtonComponent: Component {
switch component.mode {
case .none:
break
case .send, .apply, .attach, .delete, .forward:
case .send, .apply, .attach, .delete, .forward, .like:
sendAlpha = 1.0
case .more:
moreAlpha = 1.0
@ -311,7 +318,11 @@ public final class MessageInputActionButtonComponent: Component {
if self.sendIconView.image == nil || previousComponent?.mode.iconName != component.mode.iconName {
if let iconName = component.mode.iconName {
self.sendIconView.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: .white)
var tintColor: UIColor = .white
if case .like(true) = component.mode {
tintColor = UIColor(rgb: 0xFF3B30)
}
self.sendIconView.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: tintColor)
} else if case .apply = component.mode {
self.sendIconView.image = generateImage(CGSize(width: 33.0, height: 33.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
@ -407,7 +418,7 @@ public final class MessageInputActionButtonComponent: Component {
if previousComponent?.mode != component.mode {
switch component.mode {
case .none, .send, .apply, .voiceInput, .attach, .delete, .forward, .unavailableVoiceInput, .more:
case .none, .send, .apply, .voiceInput, .attach, .delete, .forward, .unavailableVoiceInput, .more, .like:
micButton.updateMode(mode: .audio, animated: !transition.animation.isImmediate)
case .videoInput:
micButton.updateMode(mode: .video, animated: !transition.animation.isImmediate)

View File

@ -79,6 +79,8 @@ public final class MessageInputPanelComponent: Component {
public let stopAndPreviewMediaRecording: (() -> Void)?
public let discardMediaRecordingPreview: (() -> Void)?
public let attachmentAction: (() -> Void)?
public let hasLike: Bool
public let likeAction: (() -> Void)?
public let inputModeAction: (() -> Void)?
public let timeoutAction: ((UIView) -> Void)?
public let forwardAction: (() -> Void)?
@ -124,6 +126,8 @@ public final class MessageInputPanelComponent: Component {
stopAndPreviewMediaRecording: (() -> Void)?,
discardMediaRecordingPreview: (() -> Void)?,
attachmentAction: (() -> Void)?,
hasLike: Bool,
likeAction: (() -> Void)?,
inputModeAction: (() -> Void)?,
timeoutAction: ((UIView) -> Void)?,
forwardAction: (() -> Void)?,
@ -168,6 +172,8 @@ public final class MessageInputPanelComponent: Component {
self.stopAndPreviewMediaRecording = stopAndPreviewMediaRecording
self.discardMediaRecordingPreview = discardMediaRecordingPreview
self.attachmentAction = attachmentAction
self.hasLike = hasLike
self.likeAction = likeAction
self.inputModeAction = inputModeAction
self.timeoutAction = timeoutAction
self.forwardAction = forwardAction
@ -271,6 +277,15 @@ public final class MessageInputPanelComponent: Component {
if lhs.disabledPlaceholder != rhs.disabledPlaceholder {
return false
}
if (lhs.attachmentAction == nil) != (rhs.attachmentAction == nil) {
return false
}
if lhs.hasLike != rhs.hasLike {
return false
}
if (lhs.likeAction == nil) != (rhs.likeAction == nil) {
return false
}
return true
}
@ -296,6 +311,7 @@ public final class MessageInputPanelComponent: Component {
private let attachmentButton = ComponentView<Empty>()
private var deleteMediaPreviewButton: ComponentView<Empty>?
private let inputActionButton = ComponentView<Empty>()
private let likeButton = ComponentView<Empty>()
private let stickerButton = ComponentView<Empty>()
private let reactionButton = ComponentView<Empty>()
private let timeoutButton = ComponentView<Empty>()
@ -325,6 +341,10 @@ public final class MessageInputPanelComponent: Component {
private var component: MessageInputPanelComponent?
private weak var state: EmptyComponentState?
public var likeButtonView: UIView? {
return self.likeButton.view
}
override init(frame: CGRect) {
self.fieldBackgroundView = BlurredBackgroundView(color: UIColor(white: 0.0, alpha: 0.5), enableBlur: true)
@ -512,7 +532,7 @@ public final class MessageInputPanelComponent: Component {
}
func update(component: MessageInputPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
var insets = UIEdgeInsets(top: 14.0, left: 7.0, bottom: 6.0, right: 41.0)
var insets = UIEdgeInsets(top: 14.0, left: 9.0, bottom: 6.0, right: 41.0)
if let _ = component.attachmentAction {
insets.left = 41.0
@ -521,10 +541,9 @@ public final class MessageInputPanelComponent: Component {
insets.right = 41.0
}
let mediaInsets = UIEdgeInsets(top: insets.top, left: 7.0, bottom: insets.bottom, right: insets.right)
let mediaInsets = UIEdgeInsets(top: insets.top, left: 9.0, bottom: insets.bottom, right: 41.0)
let baseFieldHeight: CGFloat = 40.0
self.component = component
self.state = state
@ -622,11 +641,16 @@ public final class MessageInputPanelComponent: Component {
let fieldFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: availableSize.width - insets.left - insets.right, height: textFieldSize.height))
let fieldBackgroundFrame: CGRect
var fieldBackgroundFrame: CGRect
if hasMediaRecording {
fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - mediaInsets.right, height: textFieldSize.height))
} else {
} else if isEditing {
fieldBackgroundFrame = fieldFrame
} else {
fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - insets.right, height: textFieldSize.height))
if let _ = component.likeAction {
fieldBackgroundFrame.size.width -= 49.0
}
}
transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size))
@ -803,7 +827,7 @@ public final class MessageInputPanelComponent: Component {
let attachmentButtonFrame = CGRect(origin: CGPoint(x: floor((insets.left - attachmentButtonSize.width) * 0.5) + (fieldBackgroundFrame.minX - fieldFrame.minX), y: size.height - insets.bottom - baseFieldHeight + floor((baseFieldHeight - attachmentButtonSize.height) * 0.5)), size: attachmentButtonSize)
transition.setPosition(view: attachmentButtonView, position: attachmentButtonFrame.center)
transition.setBounds(view: attachmentButtonView, bounds: CGRect(origin: CGPoint(), size: attachmentButtonFrame.size))
transition.setAlpha(view: attachmentButtonView, alpha: (hasMediaRecording || hasMediaEditing) ? 0.0 : 1.0)
transition.setAlpha(view: attachmentButtonView, alpha: (hasMediaRecording || hasMediaEditing || !isEditing) ? 0.0 : 1.0)
transition.setScale(view: attachmentButtonView, scale: hasMediaEditing ? 0.001 : 1.0)
}
}
@ -994,20 +1018,72 @@ public final class MessageInputPanelComponent: Component {
environment: {},
containerSize: CGSize(width: 33.0, height: 33.0)
)
let hasLikeAction = !(isEditing || component.likeAction == nil)
var inputActionButtonOriginX: CGFloat
if component.setMediaRecordingActive != nil || isEditing {
inputActionButtonOriginX = fieldBackgroundFrame.maxX + floorToScreenPixels((41.0 - inputActionButtonSize.width) * 0.5)
} else {
inputActionButtonOriginX = size.width
}
if hasLikeAction {
inputActionButtonOriginX += 3.0
}
if let inputActionButtonView = self.inputActionButton.view {
if inputActionButtonView.superview == nil {
self.addSubview(inputActionButtonView)
}
let inputActionButtonOriginX: CGFloat
if component.setMediaRecordingActive != nil || isEditing {
inputActionButtonOriginX = size.width - insets.right + floorToScreenPixels((insets.right - inputActionButtonSize.width) * 0.5)
} else {
inputActionButtonOriginX = size.width
}
let inputActionButtonFrame = CGRect(origin: CGPoint(x: inputActionButtonOriginX, y: size.height - insets.bottom - baseFieldHeight + floor((baseFieldHeight - inputActionButtonSize.height) * 0.5)), size: inputActionButtonSize)
transition.setPosition(view: inputActionButtonView, position: inputActionButtonFrame.center)
transition.setBounds(view: inputActionButtonView, bounds: CGRect(origin: CGPoint(), size: inputActionButtonFrame.size))
inputActionButtonOriginX += 41.0
}
let likeButtonSize = self.likeButton.update(
transition: transition,
component: AnyComponent(MessageInputActionButtonComponent(
mode: .like(isActive: component.hasLike),
action: { [weak self] _, action, _ in
guard let self, let component = self.component else {
return
}
guard case .up = action else {
return
}
component.likeAction?()
},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
},
lockMediaRecording: {
},
stopAndPreviewMediaRecording: {
},
moreAction: { _, _ in },
context: component.context,
theme: component.theme,
strings: component.strings,
presentController: component.presentController,
audioRecorder: component.audioRecorder,
videoRecordingStatus: component.videoRecordingStatus
)),
environment: {},
containerSize: CGSize(width: 33.0, height: 33.0)
)
if let likeButtonView = self.likeButton.view {
if likeButtonView.superview == nil {
self.addSubview(likeButtonView)
}
let likeButtonFrame = CGRect(origin: CGPoint(x: inputActionButtonOriginX, y: size.height - insets.bottom - baseFieldHeight + floor((baseFieldHeight - likeButtonSize.height) * 0.5)), size: likeButtonSize)
transition.setPosition(view: likeButtonView, position: likeButtonFrame.center)
transition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size))
transition.setAlpha(view: likeButtonView, alpha: hasLikeAction ? 1.0 : 0.0)
inputActionButtonOriginX += 41.0
}
var fieldIconNextX = fieldBackgroundFrame.maxX - 4.0

View File

@ -327,7 +327,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
let buttonText: String
switch component.scope {
case .saved:
buttonText = environment.strings.Common_Delete
buttonText = environment.strings.ChatList_Context_Archive
case .archive:
buttonText = environment.strings.StoryList_SaveToProfile
}
@ -350,31 +350,22 @@ final class PeerInfoStoryGridScreenComponent: Component {
switch component.scope {
case .saved:
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
let actionSheet = ActionSheetController(presentationData: presentationData)
let selectedCount = paneNode.selectedItems.count
let _ = component.context.engine.messages.updateStoriesArePinned(ids: paneNode.selectedItems, isPinned: false).start()
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
guard let self, let paneNode = self.paneNode, let component = self.component else {
return
}
let _ = component.context.engine.messages.deleteStories(ids: Array(paneNode.selectedIds)).start()
paneNode.setIsSelectionModeActive(false)
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
paneNode.setIsSelectionModeActive(false)
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
self.environment?.controller()?.present(actionSheet, in: .window(.root))
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount))
environment.controller()?.present(UndoOverlayController(
presentationData: presentationData,
content: .info(title: nil, text: title, timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
case .archive:
let _ = component.context.engine.messages.updateStoriesArePinned(ids: paneNode.selectedItems, isPinned: true).start()
@ -591,7 +582,9 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
return
}
let title: String?
if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty {
if componentView.selectedCount != 0 {
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
} else if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty {
title = paneStatusText
} else {
title = nil

View File

@ -55,6 +55,7 @@ public final class PeerListItemComponent: Component {
let subtitle: String?
let subtitleAccessory: SubtitleAccessory
let presence: EnginePeer.Presence?
let displayLike: Bool
let selectionState: SelectionState
let hasNext: Bool
let action: (EnginePeer) -> Void
@ -73,6 +74,7 @@ public final class PeerListItemComponent: Component {
subtitle: String?,
subtitleAccessory: SubtitleAccessory,
presence: EnginePeer.Presence?,
displayLike: Bool = false,
selectionState: SelectionState,
hasNext: Bool,
action: @escaping (EnginePeer) -> Void,
@ -90,6 +92,7 @@ public final class PeerListItemComponent: Component {
self.subtitle = subtitle
self.subtitleAccessory = subtitleAccessory
self.presence = presence
self.displayLike = displayLike
self.selectionState = selectionState
self.hasNext = hasNext
self.action = action
@ -131,6 +134,9 @@ public final class PeerListItemComponent: Component {
if lhs.presence != rhs.presence {
return false
}
if lhs.displayLike != rhs.displayLike {
return false
}
if lhs.selectionState != rhs.selectionState {
return false
}
@ -154,6 +160,8 @@ public final class PeerListItemComponent: Component {
private var iconView: UIImageView?
private var checkLayer: CheckLayer?
private var likeIconView: UIImageView?
private var component: PeerListItemComponent?
private weak var state: EmptyComponentState?
@ -340,7 +348,11 @@ public final class PeerListItemComponent: Component {
if case .generic = component.style {
leftInset += 9.0
}
let rightInset: CGFloat = contextInset * 2.0 + 8.0 + component.sideInset
var rightInset: CGFloat = contextInset * 2.0 + 8.0 + component.sideInset
if component.displayLike {
rightInset += 32.0
}
var avatarLeftInset: CGFloat = component.sideInset + 10.0
if case let .editing(isSelected, isTinted) = component.selectionState {
@ -567,6 +579,27 @@ public final class PeerListItemComponent: Component {
transition.setFrame(view: labelView, frame: labelFrame)
}
if component.displayLike {
let likeIconView: UIImageView
if let current = self.likeIconView {
likeIconView = current
} else {
likeIconView = UIImageView()
self.likeIconView = likeIconView
self.containerButton.addSubview(likeIconView)
likeIconView.image = PresentationResourcesChat.storyViewListLikeIcon(component.theme)
}
if let _ = likeIconView.image {
let imageSize = CGSize(width: 32.0, height: 32.0)
transition.setFrame(view: likeIconView, frame: CGRect(origin: CGPoint(x: availableSize.width - (contextInset * 2.0 + 11.0 + component.sideInset) - imageSize.width, y: floor((height - verticalInset * 2.0 - imageSize.height) * 0.5)), size: imageSize))
}
} else if let likeIconView = self.likeIconView {
self.likeIconView = nil
likeIconView.removeFromSuperview()
}
if themeUpdated {
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
}

View File

@ -172,7 +172,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
isEdited: item.isEdited,
hasLike: item.hasLike
)
}
var totalCount = peerStoryItemsView.items.count
@ -196,7 +197,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isContacts: item.privacy.base == .contacts,
isSelectedContacts: item.privacy.base == .nobody,
isForwardingDisabled: false,
isEdited: false
isEdited: false,
hasLike: false
))
totalCount += 1
}
@ -1041,7 +1043,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
isContacts: itemValue.isContacts,
isSelectedContacts: itemValue.isSelectedContacts,
isForwardingDisabled: itemValue.isForwardingDisabled,
isEdited: itemValue.isEdited
isEdited: itemValue.isEdited,
hasLike: itemValue.hasLike
)
let mainItem = StoryContentItem(

View File

@ -151,6 +151,8 @@ final class StoryContentCaptionComponent: Component {
private let collapsedText: ContentItem
private let expandedText: ContentItem
private var textSelectionNode: TextSelectionNode?
private let textSelectionKnobContainer: UIView
private var textSelectionKnobSurface: UIView?
private let scrollMaskContainer: UIView
private let scrollFullMaskView: UIView
@ -219,6 +221,9 @@ final class StoryContentCaptionComponent: Component {
self.collapsedText = ContentItem(frame: CGRect())
self.expandedText = ContentItem(frame: CGRect())
self.textSelectionKnobContainer = UIView()
self.textSelectionKnobContainer.isUserInteractionEnabled = false
super.init(frame: frame)
@ -232,6 +237,8 @@ final class StoryContentCaptionComponent: Component {
self.scrollView.addSubview(self.expandedText)
self.scrollViewContainer.mask = self.scrollMaskContainer
self.addSubview(self.textSelectionKnobContainer)
}
required init?(coder: NSCoder) {
@ -335,6 +342,8 @@ final class StoryContentCaptionComponent: Component {
let edgeDistanceFraction = edgeDistance / 7.0
transition.setAlpha(view: self.scrollFullMaskView, alpha: 1.0 - edgeDistanceFraction)
transition.setBounds(view: self.textSelectionKnobContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.scrollView.bounds.minY), size: CGSize()))
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))
@ -702,6 +711,13 @@ final class StoryContentCaptionComponent: Component {
if self.textSelectionNode == nil, let controller = component.controller(), let textNode = self.expandedText.textNode?.textNode {
let selectionColor = UIColor(white: 1.0, alpha: 0.5)
if self.textSelectionKnobSurface == nil {
let textSelectionKnobSurface = UIView()
self.textSelectionKnobSurface = textSelectionKnobSurface
self.textSelectionKnobContainer.addSubview(textSelectionKnobSurface)
}
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: component.theme.list.itemAccentColor), strings: component.strings, textNode: textNode, updateIsActive: { [weak self] value in
guard let self else {
return
@ -718,7 +734,7 @@ final class StoryContentCaptionComponent: Component {
return
}
component.controller()?.presentInGlobalOverlay(c, with: a)
}, rootNode: controller.displayNode, performAction: { [weak self] text, action in
}, rootNode: controller.displayNode, externalKnobSurface: self.textSelectionKnobSurface, performAction: { [weak self] text, action in
guard let self, let component = self.component else {
return
}
@ -806,6 +822,9 @@ final class StoryContentCaptionComponent: Component {
if let textSelectionNode = self.textSelectionNode, let textNode = self.expandedText.textNode?.textNode {
textSelectionNode.frame = textNode.frame.offsetBy(dx: self.expandedText.frame.minX, dy: self.expandedText.frame.minY)
textSelectionNode.highlightAreaNode.frame = textSelectionNode.frame
if let textSelectionKnobSurface = self.textSelectionKnobSurface {
textSelectionKnobSurface.frame = textSelectionNode.frame
}
}
self.itemLayout = ItemLayout(

View File

@ -196,6 +196,9 @@ public final class StoryItemSetContainerComponent: Component {
if lhs.context !== rhs.context {
return false
}
if lhs.availableReactions !== rhs.availableReactions {
return false
}
if lhs.slice != rhs.slice {
return false
}
@ -288,6 +291,7 @@ public final class StoryItemSetContainerComponent: Component {
final class VisibleItem {
let externalState = StoryContentItem.ExternalState()
let unclippedContainerView: UIView
let contentContainerView: UIView
let contentTintLayer = SimpleLayer()
let view = ComponentView<StoryContentItem.Environment>()
@ -296,6 +300,9 @@ public final class StoryItemSetContainerComponent: Component {
var requestedNext: Bool = false
init() {
self.unclippedContainerView = UIView()
self.unclippedContainerView.isUserInteractionEnabled = false
self.contentContainerView = UIView()
self.contentContainerView.clipsToBounds = true
if #available(iOS 13.0, *) {
@ -370,6 +377,7 @@ public final class StoryItemSetContainerComponent: Component {
let itemsContainerView: UIView
let controlsContainerView: UIView
let controlsClippingView: UIView
let topContentGradientView: UIImageView
let bottomContentGradientLayer: SimpleGradientLayer
let contentDimView: UIView
@ -453,9 +461,10 @@ public final class StoryItemSetContainerComponent: Component {
self.scroller.delaysContentTouches = false
self.controlsContainerView = SparseContainerView()
self.controlsContainerView.clipsToBounds = true
self.controlsClippingView = SparseContainerView()
self.controlsClippingView.clipsToBounds = true
if #available(iOS 13.0, *) {
self.controlsContainerView.layer.cornerCurve = .continuous
self.controlsClippingView.layer.cornerCurve = .continuous
}
self.topContentGradientView = UIImageView()
@ -486,14 +495,15 @@ public final class StoryItemSetContainerComponent: Component {
self.itemsContainerView.addGestureRecognizer(self.scroller.panGestureRecognizer)
self.addSubview(self.itemsContainerView)
self.addSubview(self.controlsClippingView)
self.addSubview(self.controlsContainerView)
self.controlsContainerView.addSubview(self.contentDimView)
self.controlsContainerView.addSubview(self.topContentGradientView)
self.controlsClippingView.addSubview(self.contentDimView)
self.controlsClippingView.addSubview(self.topContentGradientView)
self.layer.addSublayer(self.bottomContentGradientLayer)
self.closeButton.addSubview(self.closeButtonIconView)
self.controlsContainerView.addSubview(self.closeButton)
self.controlsClippingView.addSubview(self.closeButton)
self.closeButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside)
self.addSubview(self.viewListsContainer)
@ -661,6 +671,15 @@ public final class StoryItemSetContainerComponent: Component {
}
}
if self.controlsClippingView.frame.contains(point) {
if let result = self.controlsClippingView.hitTest(self.convert(point, to: self.controlsClippingView), with: nil) {
if result != self.controlsClippingView {
return false
}
}
return true
}
if self.controlsContainerView.frame.contains(point) {
if let result = self.controlsContainerView.hitTest(self.convert(point, to: self.controlsContainerView), with: nil) {
if result != self.controlsContainerView {
@ -932,9 +951,11 @@ public final class StoryItemSetContainerComponent: Component {
guard let result = super.hitTest(point, with: event) else {
return nil
}
if result === self.scroller {
return self.itemsContainerView
}
return result
}
@ -1202,6 +1223,7 @@ public final class StoryItemSetContainerComponent: Component {
if visibleItem.contentContainerView.superview == nil {
self.itemsContainerView.addSubview(visibleItem.contentContainerView)
self.itemsContainerView.layer.addSublayer(visibleItem.contentTintLayer)
self.itemsContainerView.addSubview(visibleItem.unclippedContainerView)
visibleItem.contentContainerView.addSubview(view)
}
@ -1216,10 +1238,14 @@ public final class StoryItemSetContainerComponent: Component {
if !self.trulyValidIds.contains(itemId), let visibleItem = self.visibleItems[itemId] {
self.visibleItems.removeValue(forKey: itemId)
visibleItem.contentContainerView.removeFromSuperview()
visibleItem.unclippedContainerView.removeFromSuperview()
}
})
itemTransition.setBounds(view: visibleItem.contentContainerView, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size))
itemTransition.setPosition(view: visibleItem.unclippedContainerView, position: CGPoint(x: itemPositionX, y: itemLayout.contentFrame.center.y))
itemTransition.setBounds(view: visibleItem.unclippedContainerView, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size))
itemTransition.setPosition(layer: visibleItem.contentTintLayer, position: CGPoint(x: itemPositionX, y: itemLayout.contentFrame.center.y))
itemTransition.setBounds(layer: visibleItem.contentTintLayer, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size))
@ -1240,6 +1266,8 @@ public final class StoryItemSetContainerComponent: Component {
itemTransition.setTransform(view: visibleItem.contentContainerView, transform: transform)
itemTransition.setCornerRadius(layer: visibleItem.contentContainerView.layer, cornerRadius: 12.0 * (1.0 / itemScale))
itemTransition.setTransform(view: visibleItem.unclippedContainerView, transform: transform)
itemTransition.setTransform(layer: visibleItem.contentTintLayer, transform: transform)
let countedFractionDistanceToCenter: CGFloat = max(0.0, min(1.0, unboundFractionDistanceToCenter / 3.0))
@ -1270,6 +1298,7 @@ public final class StoryItemSetContainerComponent: Component {
if !validIds.contains(id) {
removeIds.append(id)
visibleItem.contentContainerView.removeFromSuperview()
visibleItem.unclippedContainerView.removeFromSuperview()
visibleItem.contentTintLayer.removeFromSuperlayer()
}
}
@ -1390,7 +1419,10 @@ public final class StoryItemSetContainerComponent: Component {
captionItemView.layer.animateAlpha(from: 0.0, to: captionItemView.alpha, duration: 0.28)
}
if let component = self.component, let sourceView = transitionIn.sourceView, let contentContainerView = self.visibleItems[component.slice.item.storyItem.id]?.contentContainerView {
if let component = self.component, let sourceView = transitionIn.sourceView, let visibleItem = self.visibleItems[component.slice.item.storyItem.id] {
let contentContainerView = visibleItem.contentContainerView
let unclippedContainerView = visibleItem.unclippedContainerView
if let centerInfoView = self.centerInfoItem?.view.view {
centerInfoView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
@ -1431,11 +1463,17 @@ public final class StoryItemSetContainerComponent: Component {
duration: 0.3
)
unclippedContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: contentContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
unclippedContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: contentContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.controlsContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.controlsContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.controlsContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: self.controlsContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.controlsContainerView.layer.animate(
self.controlsClippingView.layer.animatePosition(from: sourceLocalFrame.center, to: self.controlsClippingView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.controlsClippingView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: self.controlsClippingView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.controlsClippingView.layer.animate(
from: transitionIn.sourceCornerRadius as NSNumber,
to: self.controlsContainerView.layer.cornerRadius as NSNumber,
to: self.controlsClippingView.layer.cornerRadius as NSNumber,
keyPath: "cornerRadius",
timingFunction: kCAMediaTimingFunctionSpring,
duration: 0.3
@ -1532,7 +1570,10 @@ public final class StoryItemSetContainerComponent: Component {
})
}
if let component = self.component, let sourceView = transitionOut.destinationView, let contentContainerView = self.visibleItems[component.slice.item.storyItem.id]?.contentContainerView {
if let component = self.component, let sourceView = transitionOut.destinationView, let visibleItem = self.visibleItems[component.slice.item.storyItem.id] {
let contentContainerView = visibleItem.contentContainerView
let unclippedContainerView = visibleItem.unclippedContainerView
let sourceLocalFrame = sourceView.convert(transitionOut.destinationRect, to: self)
let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - contentContainerView.frame.minX, y: sourceLocalFrame.minY - contentContainerView.frame.minY), size: sourceLocalFrame.size)
@ -1614,7 +1655,9 @@ public final class StoryItemSetContainerComponent: Component {
}
contentContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
unclippedContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.controlsContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.controlsClippingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
for transitionViewImpl in transitionViewsImpl {
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
@ -1652,10 +1695,16 @@ public final class StoryItemSetContainerComponent: Component {
removeOnCompletion: false
)
unclippedContainerView.layer.animatePosition(from: contentContainerView.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
unclippedContainerView.layer.animateBounds(from: contentContainerView.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.controlsContainerView.layer.animatePosition(from: self.controlsContainerView.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.controlsContainerView.layer.animateBounds(from: self.controlsContainerView.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.controlsContainerView.layer.animate(
from: self.controlsContainerView.layer.cornerRadius as NSNumber,
self.controlsClippingView.layer.animatePosition(from: self.controlsClippingView.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.controlsClippingView.layer.animateBounds(from: self.controlsClippingView.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.controlsClippingView.layer.animate(
from: self.controlsClippingView.layer.cornerRadius as NSNumber,
to: transitionOut.destinationCornerRadius as NSNumber,
keyPath: "cornerRadius",
timingFunction: kCAMediaTimingFunctionSpring,
@ -1716,7 +1765,9 @@ public final class StoryItemSetContainerComponent: Component {
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
}
contentContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
unclippedContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.controlsContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.controlsClippingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
for transitionViewImpl in transitionViewsImpl {
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
@ -1989,6 +2040,13 @@ public final class StoryItemSetContainerComponent: Component {
}
self.sendMessageContext.presentAttachmentMenu(view: self, subject: .default)
},
hasLike: component.slice.item.storyItem.hasLike,
likeAction: component.slice.peer.isService ? nil : { [weak self] in
guard let self else {
return
}
self.performLikeAction()
},
inputModeAction: { [weak self] in
guard let self else {
return
@ -2378,12 +2436,13 @@ public final class StoryItemSetContainerComponent: Component {
}
if isContact {
//TODO:localize
itemList.append(.action(ContextMenuActionItem(text: "Delete Contact", textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, f in
f(.default)
let _ = component.context.engine.contacts.deleteContactPeerInteractively(peerId: peer.id)
let _ = component.context.engine.contacts.deleteContactPeerInteractively(peerId: peer.id).start()
guard let self else {
return
@ -2568,6 +2627,9 @@ public final class StoryItemSetContainerComponent: Component {
transition.setPosition(view: self.controlsContainerView, position: contentFrame.center)
transition.setBounds(view: self.controlsContainerView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size))
transition.setPosition(view: self.controlsClippingView, position: contentFrame.center)
transition.setBounds(view: self.controlsClippingView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size))
var transform = CATransform3DMakeScale(contentVisualScale, contentVisualScale, 1.0)
if let pinchState = component.pinchState {
let pinchOffset = CGPoint(
@ -2583,8 +2645,9 @@ public final class StoryItemSetContainerComponent: Component {
transform = CATransform3DScale(transform, pinchState.scale, pinchState.scale, 0.0)
}
transition.setTransform(view: self.controlsContainerView, transform: transform)
transition.setTransform(view: self.controlsClippingView, transform: transform)
transition.setCornerRadius(layer: self.controlsContainerView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale))
transition.setCornerRadius(layer: self.controlsClippingView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale))
var headerRightOffset: CGFloat = availableSize.width
@ -2630,7 +2693,7 @@ public final class StoryItemSetContainerComponent: Component {
)
if let moreButtonView = self.moreButton.view {
if moreButtonView.superview == nil {
self.controlsContainerView.addSubview(moreButtonView)
self.controlsClippingView.addSubview(moreButtonView)
}
moreButtonView.isUserInteractionEnabled = !component.slice.item.storyItem.isPending
transition.setFrame(view: moreButtonView, frame: CGRect(origin: CGPoint(x: headerRightOffset - moreButtonSize.width, y: 2.0), size: moreButtonSize))
@ -2697,7 +2760,7 @@ public final class StoryItemSetContainerComponent: Component {
if let soundButtonView = self.soundButton.view {
if soundButtonView.superview == nil {
self.controlsContainerView.addSubview(soundButtonView)
self.controlsClippingView.addSubview(soundButtonView)
}
transition.setFrame(view: soundButtonView, frame: CGRect(origin: CGPoint(x: headerRightOffset - soundButtonSize.width, y: 2.0), size: soundButtonSize))
transition.setAlpha(view: soundButtonView, alpha: soundAlpha)
@ -2799,7 +2862,7 @@ public final class StoryItemSetContainerComponent: Component {
let closeFriendIconFrame = CGRect(origin: CGPoint(x: headerRightOffset - privacyIconSize.width - 8.0, y: 22.0), size: privacyIconSize)
if let closeFriendIconView = privacyIcon.view {
if closeFriendIconView.superview == nil {
self.controlsContainerView.addSubview(closeFriendIconView)
self.controlsClippingView.addSubview(closeFriendIconView)
}
privacyIconTransition.setFrame(view: closeFriendIconView, frame: closeFriendIconFrame)
@ -2810,7 +2873,9 @@ public final class StoryItemSetContainerComponent: Component {
closeFriendIcon.view?.removeFromSuperview()
}
transition.setAlpha(view: self.controlsContainerView, alpha: (component.hideUI || self.isEditingStory || self.displayViewList) ? 0.0 : 1.0)
let controlsContainerAlpha = (component.hideUI || self.isEditingStory || self.displayViewList) ? 0.0 : 1.0
transition.setAlpha(view: self.controlsContainerView, alpha: controlsContainerAlpha)
transition.setAlpha(view: self.controlsClippingView, alpha: controlsContainerAlpha)
let focusedItem: StoryContentItem? = component.slice.item
@ -2887,7 +2952,7 @@ public final class StoryItemSetContainerComponent: Component {
if let view = currentCenterInfoItem.view.view {
var animateIn = false
if view.superview == nil {
self.controlsContainerView.insertSubview(view, belowSubview: self.closeButton)
self.controlsClippingView.insertSubview(view, belowSubview: self.closeButton)
animateIn = true
}
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: centerInfoItemSize))
@ -2921,7 +2986,7 @@ public final class StoryItemSetContainerComponent: Component {
if let view = currentLeftInfoItem.view.view {
var animateIn = false
if view.superview == nil {
self.controlsContainerView.addSubview(view)
self.controlsClippingView.addSubview(view)
animateIn = true
}
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 12.0, y: 18.0), size: leftInfoItemSize))
@ -3469,7 +3534,7 @@ public final class StoryItemSetContainerComponent: Component {
if let navigationStripView = self.navigationStrip.view {
if navigationStripView.superview == nil {
navigationStripView.isUserInteractionEnabled = false
self.controlsContainerView.addSubview(navigationStripView)
self.controlsClippingView.addSubview(navigationStripView)
}
transition.setFrame(view: navigationStripView, frame: CGRect(origin: CGPoint(x: navigationStripSideInset, y: navigationStripTopInset), size: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0)))
transition.setAlpha(view: navigationStripView, alpha: self.isEditingStory ? 0.0 : 1.0)
@ -3997,6 +4062,65 @@ public final class StoryItemSetContainerComponent: Component {
}
}
private func performLikeAction() {
guard let component = self.component else {
return
}
guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else {
return
}
guard let likeButtonView = inputPanelView.likeButtonView else {
return
}
let _ = component.context.engine.messages.setStoryLike(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, hasLike: !component.slice.item.storyItem.hasLike).start()
if component.slice.item.storyItem.hasLike {
return
}
var reactionItem: ReactionItem?
guard let availableReactions = component.availableReactions else {
return
}
for item in availableReactions.reactionItems {
if case .builtin("") = item.reaction.rawValue {
reactionItem = item
break
}
}
guard let reactionItem else {
return
}
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false)
self.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = self.bounds
standaloneReactionAnimation.animateReactionSelection(
context: component.context,
theme: component.theme,
animationCache: component.context.animationCache,
reaction: reactionItem,
avatarPeers: [],
playHaptic: true,
isLarge: false,
hideCenterAnimation: true,
targetView: likeButtonView,
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
guard let self else {
return
}
standaloneReactionAnimation.frame = self.bounds
self.addSubnode(standaloneReactionAnimation)
},
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
func dismissAllTooltips() {
guard let component = self.component, let controller = component.controller() else {
return

View File

@ -517,6 +517,7 @@ final class StoryItemSetViewListComponent: Component {
subtitle: dateText,
subtitleAccessory: .checks,
presence: nil,
displayLike: item.isLike,
selectionState: .none,
hasNext: index != viewListState.totalCount - 1,
action: { [weak self] peer in

View File

@ -297,7 +297,7 @@ public final class StoryFooterPanelComponent: Component {
if viewCount != 0 {
regularSegments.append(.number(viewCount, NSAttributedString(string: "\(viewCount)", font: Font.regular(15.0), textColor: .white)))
}
regularSegments.append(.text(1, NSAttributedString(string: " Views", font: Font.regular(15.0), textColor: .white)))
regularSegments.append(.text(1, NSAttributedString(string: viewCount == 1 ? " View" : " Views", font: Font.regular(15.0), textColor: .white)))
var expandedSegments: [AnimatedCountLabelView.Segment] = []
if viewCount != 0 {

View File

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

View File

@ -0,0 +1,135 @@
%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 4.375000 3.032455 cm
0.000000 0.000000 0.000000 scn
11.603342 2.059748 m
12.047447 1.358528 l
12.052738 1.361937 l
11.603342 2.059748 l
h
10.625000 18.449600 m
9.893637 18.057159 l
10.037730 17.788624 10.317400 17.620649 10.622152 17.619604 c
10.926906 17.618559 11.207721 17.784609 11.353653 18.052153 c
10.625000 18.449600 l
h
9.646659 2.059748 m
9.197262 1.361937 l
9.202449 1.358595 9.207672 1.355312 9.212932 1.352089 c
9.646659 2.059748 l
h
10.625000 0.829996 m
10.934997 0.829996 11.227332 0.935177 11.431684 1.024776 c
11.653996 1.122252 11.868692 1.245344 12.047435 1.358549 c
11.159248 2.760948 l
11.022397 2.674276 10.884680 2.597492 10.765099 2.545061 c
10.706002 2.519150 10.660861 2.503416 10.629790 2.494913 c
10.596159 2.485710 10.597184 2.489996 10.625000 2.489996 c
10.625000 0.829996 l
h
12.052738 1.361937 m
17.971605 5.173729 22.080000 9.829360 22.080000 14.788708 c
20.420000 14.788708 l
20.420000 10.743106 16.996216 6.520025 11.153945 2.757561 c
12.052738 1.361937 l
h
22.080000 14.788708 m
22.080000 19.057514 19.095673 22.172544 15.232674 22.172544 c
15.232674 20.512545 l
18.081306 20.512545 20.420000 18.241436 20.420000 14.788708 c
22.080000 14.788708 l
h
15.232674 22.172544 m
12.780071 22.172544 10.959950 20.796986 9.896347 18.847046 c
11.353653 18.052153 l
12.183614 19.573746 13.498394 20.512545 15.232674 20.512545 c
15.232674 22.172544 l
h
11.356363 18.842037 m
10.312891 20.786690 8.479945 22.172544 6.027846 22.172544 c
6.027846 20.512545 l
7.762629 20.512545 9.085624 19.563004 9.893637 18.057159 c
11.356363 18.842037 l
h
6.027846 22.172544 m
2.166678 22.172544 -0.830000 19.059464 -0.830000 14.788708 c
0.830000 14.788708 l
0.830000 18.239487 3.177382 20.512545 6.027846 20.512545 c
6.027846 22.172544 l
h
-0.830000 14.788708 m
-0.830000 9.829360 3.278394 5.173729 9.197262 1.361937 c
10.096055 2.757561 l
4.253784 6.520025 0.830000 10.743106 0.830000 14.788708 c
-0.830000 14.788708 l
h
9.212932 1.352089 m
9.392480 1.242044 9.607049 1.120869 9.826206 1.024776 c
10.024843 0.937681 10.316897 0.829996 10.625000 0.829996 c
10.625000 2.489996 l
10.658530 2.489996 10.663249 2.484592 10.629533 2.493975 c
10.598954 2.502481 10.553483 2.518450 10.492792 2.545061 c
10.370055 2.598877 10.226951 2.677576 10.080385 2.767408 c
9.212932 1.352089 l
h
f
n
Q
endstream
endobj
3 0 obj
2357
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.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
0000002447 00000 n
0000002470 00000 n
0000002643 00000 n
0000002717 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2776
%%EOF

View File

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

View File

@ -0,0 +1,75 @@
%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 4.375000 4.692444 cm
0.000000 0.000000 0.000000 scn
10.625000 0.000008 m
10.898515 0.000008 11.287747 0.199884 11.603342 0.399759 c
17.483912 4.186889 21.250000 8.626245 21.250000 13.128719 c
21.250000 16.989487 18.588490 19.682556 15.232674 19.682556 c
13.139233 19.682556 11.571782 18.525377 10.625000 16.789612 c
9.699258 18.514858 8.121287 19.682556 6.027846 19.682556 c
2.672030 19.682556 0.000000 16.989487 0.000000 13.128719 c
0.000000 8.626245 3.766089 4.186889 9.646659 0.399759 c
9.972773 0.199884 10.362005 0.000008 10.625000 0.000008 c
h
f
n
Q
endstream
endobj
3 0 obj
623
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.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
0000000713 00000 n
0000000735 00000 n
0000000908 00000 n
0000000982 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1041
%%EOF

View File

@ -103,6 +103,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
var skipText = false
var messageWithCaptionToAdd: (Message, ChatMessageEntryAttributes)?
var isUnsupportedMedia = false
var isStoryWithText = false
var isAction = false
var previousItemIsFile = false
@ -140,6 +141,12 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
} else {
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
}
if let storyItem = message.associatedStories[story.storyId], let storedItem = storyItem.get(Stories.StoredItem.self), case let .item(item) = storedItem {
if !item.text.isEmpty {
isStoryWithText = true
}
}
}
} else if let file = media as? TelegramMediaFile {
let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil)
@ -216,7 +223,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
messageText = updatingMedia.text
}
if !messageText.isEmpty || isUnsupportedMedia {
if !messageText.isEmpty || isUnsupportedMedia || isStoryWithText {
if !skipText {
if case .group = item.content, !isFile {
messageWithCaptionToAdd = (message, itemAttributes)

View File

@ -218,6 +218,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
var mediaDuration: Double? = nil
var isSeekableWebMedia = false
var isUnsupportedMedia = false
var story: Stories.Item?
for media in item.message.media {
if let file = media as? TelegramMediaFile, let duration = file.duration {
mediaDuration = Double(duration)
@ -226,11 +227,20 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
isSeekableWebMedia = true
} else if media is TelegramMediaUnsupported {
isUnsupportedMedia = true
} else if let storyMedia = media as? TelegramMediaStory {
if let value = item.message.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) {
if case let .item(storyValue) = value {
story = storyValue
}
}
}
}
var isTranslating = false
if isUnsupportedMedia {
if let story {
rawText = story.text
messageEntities = story.entities
} else if isUnsupportedMedia {
rawText = item.presentationData.strings.Conversation_UnsupportedMediaPlaceholder
messageEntities = [MessageTextEntity(range: 0..<rawText.count, type: .Italic)]
} else {

View File

@ -240,7 +240,7 @@ public final class TextSelectionNode: ASDisplayNode {
return self.recognizer?.didRecognizeTap ?? false
}
public init(theme: TextSelectionTheme, strings: PresentationStrings, textNode: TextNode, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: ASDisplayNode, performAction: @escaping (NSAttributedString, TextSelectionAction) -> Void) {
public init(theme: TextSelectionTheme, strings: PresentationStrings, textNode: TextNode, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: ASDisplayNode, externalKnobSurface: UIView? = nil, performAction: @escaping (NSAttributedString, TextSelectionAction) -> Void) {
self.theme = theme
self.strings = strings
self.textNode = textNode
@ -269,8 +269,13 @@ public final class TextSelectionNode: ASDisplayNode {
return TextSelectionNodeView()
})
self.addSubnode(self.leftKnob)
self.addSubnode(self.rightKnob)
if let externalKnobSurface {
externalKnobSurface.addSubnode(self.leftKnob)
externalKnobSurface.addSubnode(self.rightKnob)
} else {
self.addSubnode(self.leftKnob)
self.addSubnode(self.rightKnob)
}
}
override public func didLoad() {
@ -449,9 +454,11 @@ public final class TextSelectionNode: ASDisplayNode {
} else {
highlightOverlay = LinkHighlightingNode(color: self.theme.selection)
highlightOverlay.isUserInteractionEnabled = false
highlightOverlay.innerRadius = 0.0
highlightOverlay.outerRadius = 0.0
highlightOverlay.innerRadius = 2.0
highlightOverlay.outerRadius = 2.0
highlightOverlay.inset = 1.0
highlightOverlay.useModernPathCalculation = true
self.highlightOverlay = highlightOverlay
self.highlightAreaNode.addSubnode(highlightOverlay)
}