mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Stories
This commit is contained in:
parent
5665d89419
commit
ae49ad3700
@ -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.";
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,6 +295,8 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatGeneralThreadFreeIcon
|
||||
|
||||
case uploadToneIcon
|
||||
|
||||
case storyViewListLikeIcon
|
||||
}
|
||||
|
||||
public enum ChatExpiredStoryIndicatorType: Hashable {
|
||||
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -266,6 +266,8 @@ final class StoryPreviewComponent: Component {
|
||||
stopAndPreviewMediaRecording: nil,
|
||||
discardMediaRecordingPreview: nil,
|
||||
attachmentAction: { },
|
||||
hasLike: false,
|
||||
likeAction: nil,
|
||||
inputModeAction: nil,
|
||||
timeoutAction: nil,
|
||||
forwardAction: {},
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Stories/InputLikeOff.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Stories/InputLikeOff.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "like_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
135
submodules/TelegramUI/Images.xcassets/Stories/InputLikeOff.imageset/like_30.pdf
vendored
Normal file
135
submodules/TelegramUI/Images.xcassets/Stories/InputLikeOff.imageset/like_30.pdf
vendored
Normal 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
|
12
submodules/TelegramUI/Images.xcassets/Stories/InputLikeOn.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Stories/InputLikeOn.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heartfilled_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
75
submodules/TelegramUI/Images.xcassets/Stories/InputLikeOn.imageset/heartfilled_30.pdf
vendored
Normal file
75
submodules/TelegramUI/Images.xcassets/Stories/InputLikeOn.imageset/heartfilled_30.pdf
vendored
Normal 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
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user