mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
3e0c38c3ce
commit
796826b0ad
12 changed files with 766 additions and 804 deletions
|
|
@ -67,6 +67,7 @@ public final class PeerSelectionControllerParams {
|
|||
public let hasCreation: Bool
|
||||
public let immediatelySwitchToContacts: Bool
|
||||
public let immediatelyActivateMultipleSelection: Bool
|
||||
public let suggestedPeers: [EnginePeer]
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
|
|
@ -89,7 +90,8 @@ public final class PeerSelectionControllerParams {
|
|||
selectForumThreads: Bool = false,
|
||||
hasCreation: Bool = false,
|
||||
immediatelySwitchToContacts: Bool = false,
|
||||
immediatelyActivateMultipleSelection: Bool = false
|
||||
immediatelyActivateMultipleSelection: Bool = false,
|
||||
suggestedPeers: [EnginePeer] = []
|
||||
) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
|
|
@ -112,6 +114,7 @@ public final class PeerSelectionControllerParams {
|
|||
self.hasCreation = hasCreation
|
||||
self.immediatelySwitchToContacts = immediatelySwitchToContacts
|
||||
self.immediatelyActivateMultipleSelection = immediatelyActivateMultipleSelection
|
||||
self.suggestedPeers = suggestedPeers
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import GlobalControlPanelsContext
|
|||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList(appendContacts: Bool)
|
||||
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, displayPresence: Bool)
|
||||
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], topPeers: [EnginePeer], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, displayPresence: Bool)
|
||||
case peerType(type: [ReplyMarkupButtonRequestPeerType], hasCreate: Bool)
|
||||
}
|
||||
|
||||
|
|
@ -372,286 +372,268 @@ public struct ChatListNodeState: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||
private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionInsertEntry], presentationData: ChatListPresentationData) -> [ListViewInsertItem] {
|
||||
return entries.map { entry -> ListViewInsertItem in
|
||||
switch entry.entry {
|
||||
case .HeaderEntry:
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
||||
case let .AdditionalCategory(_, id, title, image, appearance, selected, presentationData):
|
||||
var header: ChatListSearchItemHeader?
|
||||
if case .peerType = mode {
|
||||
} else {
|
||||
if case .action = appearance {
|
||||
// TODO: hack, generalize
|
||||
header = ChatListSearchItemHeader(type: .orImportIntoAnExistingGroup, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
}
|
||||
case .HeaderEntry:
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
||||
case let .AdditionalCategory(_, id, title, image, appearance, selected, presentationData):
|
||||
var header: ChatListSearchItemHeader?
|
||||
if case .peerType = mode {
|
||||
} else {
|
||||
if case .action = appearance {
|
||||
// TODO: hack, generalize
|
||||
header = ChatListSearchItemHeader(type: .orImportIntoAnExistingGroup, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
}
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
context: context,
|
||||
title: title,
|
||||
image: image,
|
||||
appearance: appearance,
|
||||
isSelected: selected,
|
||||
header: header,
|
||||
action: {
|
||||
nodeInteraction.additionalCategorySelected(id)
|
||||
}
|
||||
), directionHint: entry.directionHint)
|
||||
case let .PeerEntry(peerEntry):
|
||||
let index = peerEntry.index
|
||||
let presentationData = peerEntry.presentationData
|
||||
let combinedReadState = peerEntry.readState
|
||||
let isRemovedFromTotalUnreadCount = peerEntry.isRemovedFromTotalUnreadCount
|
||||
let draftState = peerEntry.draftState
|
||||
let peer = peerEntry.peer
|
||||
let threadInfo = peerEntry.threadInfo
|
||||
let presence = peerEntry.presence
|
||||
let hasUnseenMentions = peerEntry.hasUnseenMentions
|
||||
let hasUnseenReactions = peerEntry.hasUnseenReactions
|
||||
let hasUnseenPollVotes = peerEntry.hasUnseenPollVotes
|
||||
let editing = peerEntry.editing
|
||||
let hasActiveRevealControls = peerEntry.hasActiveRevealControls
|
||||
let selected = peerEntry.selected
|
||||
let inputActivities = peerEntry.inputActivities
|
||||
let promoInfo = peerEntry.promoInfo
|
||||
let hasFailedMessages = peerEntry.hasFailedMessages
|
||||
let isContact = peerEntry.isContact
|
||||
let forumTopicData = peerEntry.forumTopicData
|
||||
let topForumTopicItems = peerEntry.topForumTopicItems
|
||||
let revealed = peerEntry.revealed
|
||||
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
||||
presentationData: presentationData,
|
||||
context: context,
|
||||
chatListLocation: location,
|
||||
filterData: filterData,
|
||||
index: index,
|
||||
content: .peer(ChatListItemContent.PeerData(
|
||||
messages: peerEntry.messages,
|
||||
peer: peer,
|
||||
threadInfo: threadInfo,
|
||||
combinedReadState: combinedReadState,
|
||||
isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount,
|
||||
presence: presence,
|
||||
hasUnseenMentions: hasUnseenMentions,
|
||||
hasUnseenReactions: hasUnseenReactions,
|
||||
hasUnseenPollVotes: hasUnseenPollVotes,
|
||||
draftState: draftState,
|
||||
mediaDraftContentType: peerEntry.mediaDraftContentType,
|
||||
inputActivities: inputActivities,
|
||||
promoInfo: promoInfo,
|
||||
ignoreUnreadBadge: false,
|
||||
displayAsMessage: false,
|
||||
hasFailedMessages: hasFailedMessages,
|
||||
forumTopicData: forumTopicData,
|
||||
topForumTopicItems: topForumTopicItems,
|
||||
autoremoveTimeout: peerEntry.autoremoveTimeout,
|
||||
storyState: peerEntry.storyState.flatMap { storyState in
|
||||
return ChatListItemContent.StoryState(
|
||||
stats: storyState.stats,
|
||||
hasUnseenCloseFriends: storyState.hasUnseenCloseFriends
|
||||
)
|
||||
},
|
||||
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
|
||||
displayAsTopicList: peerEntry.displayAsTopicList,
|
||||
tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, isPremium: isPremium, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters)
|
||||
)),
|
||||
editing: editing,
|
||||
hasActiveRevealControls: hasActiveRevealControls,
|
||||
selected: selected,
|
||||
header: nil,
|
||||
enabledContextActions: .auto,
|
||||
hiddenOffset: threadInfo?.isHidden == true && !revealed,
|
||||
interaction: nodeInteraction
|
||||
), directionHint: entry.directionHint)
|
||||
case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence):
|
||||
let itemPeer = peer.chatOrMonoforumMainPeer
|
||||
var chatPeer: EnginePeer?
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
chatPeer = peer
|
||||
}
|
||||
var enabled = true
|
||||
if let isPeerEnabled {
|
||||
if let itemPeer {
|
||||
enabled = isPeerEnabled(itemPeer)
|
||||
}
|
||||
} else {
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
enabled = false
|
||||
}
|
||||
if peerEntry.requiresPremiumForMessaging {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer {
|
||||
if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyPrivateChats) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
switch peer {
|
||||
case .user, .secretChat:
|
||||
break
|
||||
default:
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyGroups) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if case .legacyGroup = peer {
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyManageable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
var canManage = false
|
||||
if case let .legacyGroup(peer) = peer {
|
||||
switch peer.role {
|
||||
case .creator, .admin:
|
||||
canManage = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
||||
canManage = true
|
||||
} else if case let .channel(peer) = peer, case .broadcast = peer.info, peer.hasPermission(.addAdmins) {
|
||||
canManage = true
|
||||
}
|
||||
|
||||
if canManage {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if case let .channel(peer) = peer, case .broadcast = peer.info {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var header: ChatListSearchItemHeader?
|
||||
switch mode {
|
||||
case let .peers(_, _, additionalCategories, _, _, _):
|
||||
if !additionalCategories.isEmpty {
|
||||
let headerType: ChatListSearchItemHeaderType
|
||||
if case .action = additionalCategories[0].appearance {
|
||||
// TODO: hack, generalize
|
||||
headerType = .orImportIntoAnExistingGroup
|
||||
} else {
|
||||
headerType = .chats
|
||||
}
|
||||
header = ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
var status: ContactsPeerItemStatus = .none
|
||||
if isSelecting, let itemPeer = itemPeer {
|
||||
if displayPresence, let presence = presence {
|
||||
status = .presence(presence, presentationData.dateTimeFormat)
|
||||
} else if let (string, multiline, isActive, icon) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters, displayAutoremoveTimeout: displayAutoremoveTimeout, autoremoveTimeout: peerEntry.autoremoveTimeout) {
|
||||
status = .custom(string: string, multiline: multiline, isActive: isActive, icon: icon)
|
||||
} else {
|
||||
status = .none
|
||||
}
|
||||
}
|
||||
|
||||
let peerContent: ContactsPeerItemPeer
|
||||
if let threadInfo = threadInfo, let itemPeer = itemPeer {
|
||||
peerContent = .thread(peer: itemPeer, title: threadInfo.info.title, icon: threadInfo.info.icon, color: threadInfo.info.iconColor)
|
||||
} else {
|
||||
peerContent = .peer(peer: itemPeer, chatPeer: chatPeer)
|
||||
}
|
||||
|
||||
var threadId: Int64?
|
||||
switch index {
|
||||
case let .forum(_, _, threadIdValue, _, _):
|
||||
threadId = threadIdValue
|
||||
case .chatList:
|
||||
break
|
||||
}
|
||||
|
||||
var isForum = false
|
||||
if let peer = chatPeer, case let .channel(channel) = peer, channel.isForumOrMonoForum {
|
||||
isForum = true
|
||||
if editing, case .chatList = mode {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
var selectable = editing
|
||||
if case .chatList = mode {
|
||||
if isForum {
|
||||
selectable = false
|
||||
}
|
||||
}
|
||||
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
|
||||
enabled: enabled,
|
||||
selection: selectable ? .selectable(selected: selected) : .none,
|
||||
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||
index: nil,
|
||||
header: header,
|
||||
action: { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
if editing {
|
||||
nodeInteraction.togglePeerSelected(chatPeer, threadId)
|
||||
} else {
|
||||
nodeInteraction.peerSelected(chatPeer, nil, threadId, nil, false)
|
||||
}
|
||||
}
|
||||
}, disabledAction: (isForum && editing) && !peerEntry.requiresPremiumForMessaging ? nil : { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
nodeInteraction.disabledPeerSelected(chatPeer, threadId, peerEntry.requiresPremiumForMessaging ? .premiumRequired : .generic)
|
||||
}
|
||||
}
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
context: context,
|
||||
title: title,
|
||||
image: image,
|
||||
appearance: appearance,
|
||||
isSelected: selected,
|
||||
header: header,
|
||||
action: {
|
||||
nodeInteraction.additionalCategorySelected(id)
|
||||
}
|
||||
), directionHint: entry.directionHint)
|
||||
case let .TopPeer(_, peer):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: .peer(peer: peer, chatPeer: peer),
|
||||
status: .none,
|
||||
requiresPremiumForMessaging: false,
|
||||
enabled: true,
|
||||
selection: .none,
|
||||
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||
index: nil,
|
||||
header: nil,
|
||||
action: { _ in
|
||||
nodeInteraction.peerSelected(peer, nil, nil, nil, false)
|
||||
},
|
||||
disabledAction: nil,
|
||||
animationCache: nodeInteraction.animationCache,
|
||||
animationRenderer: nodeInteraction.animationRenderer
|
||||
), directionHint: entry.directionHint)
|
||||
case let .PeerEntry(peerEntry):
|
||||
let index = peerEntry.index
|
||||
let presentationData = peerEntry.presentationData
|
||||
let combinedReadState = peerEntry.readState
|
||||
let isRemovedFromTotalUnreadCount = peerEntry.isRemovedFromTotalUnreadCount
|
||||
let draftState = peerEntry.draftState
|
||||
let peer = peerEntry.peer
|
||||
let threadInfo = peerEntry.threadInfo
|
||||
let presence = peerEntry.presence
|
||||
let hasUnseenMentions = peerEntry.hasUnseenMentions
|
||||
let hasUnseenReactions = peerEntry.hasUnseenReactions
|
||||
let hasUnseenPollVotes = peerEntry.hasUnseenPollVotes
|
||||
let editing = peerEntry.editing
|
||||
let hasActiveRevealControls = peerEntry.hasActiveRevealControls
|
||||
let selected = peerEntry.selected
|
||||
let inputActivities = peerEntry.inputActivities
|
||||
let promoInfo = peerEntry.promoInfo
|
||||
let hasFailedMessages = peerEntry.hasFailedMessages
|
||||
let isContact = peerEntry.isContact
|
||||
let forumTopicData = peerEntry.forumTopicData
|
||||
let topForumTopicItems = peerEntry.topForumTopicItems
|
||||
let revealed = peerEntry.revealed
|
||||
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
||||
presentationData: presentationData,
|
||||
context: context,
|
||||
chatListLocation: location,
|
||||
filterData: filterData,
|
||||
index: index,
|
||||
content: .peer(ChatListItemContent.PeerData(
|
||||
messages: peerEntry.messages,
|
||||
peer: peer,
|
||||
threadInfo: threadInfo,
|
||||
combinedReadState: combinedReadState,
|
||||
isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount,
|
||||
presence: presence,
|
||||
hasUnseenMentions: hasUnseenMentions,
|
||||
hasUnseenReactions: hasUnseenReactions,
|
||||
hasUnseenPollVotes: hasUnseenPollVotes,
|
||||
draftState: draftState,
|
||||
mediaDraftContentType: peerEntry.mediaDraftContentType,
|
||||
inputActivities: inputActivities,
|
||||
promoInfo: promoInfo,
|
||||
ignoreUnreadBadge: false,
|
||||
displayAsMessage: false,
|
||||
hasFailedMessages: hasFailedMessages,
|
||||
forumTopicData: forumTopicData,
|
||||
topForumTopicItems: topForumTopicItems,
|
||||
autoremoveTimeout: peerEntry.autoremoveTimeout,
|
||||
storyState: peerEntry.storyState.flatMap { storyState in
|
||||
return ChatListItemContent.StoryState(
|
||||
stats: storyState.stats,
|
||||
hasUnseenCloseFriends: storyState.hasUnseenCloseFriends
|
||||
)
|
||||
},
|
||||
animationCache: nodeInteraction.animationCache,
|
||||
animationRenderer: nodeInteraction.animationRenderer
|
||||
), directionHint: entry.directionHint)
|
||||
case .peerType:
|
||||
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
|
||||
displayAsTopicList: peerEntry.displayAsTopicList,
|
||||
tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, isPremium: isPremium, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters)
|
||||
)),
|
||||
editing: editing,
|
||||
hasActiveRevealControls: hasActiveRevealControls,
|
||||
selected: selected,
|
||||
header: nil,
|
||||
enabledContextActions: .auto,
|
||||
hiddenOffset: threadInfo?.isHidden == true && !revealed,
|
||||
interaction: nodeInteraction
|
||||
), directionHint: entry.directionHint)
|
||||
case let .peers(filter, isSelecting, _, _, filters, displayAutoremoveTimeout, displayPresence):
|
||||
let itemPeer = peer.chatOrMonoforumMainPeer
|
||||
var chatPeer: EnginePeer?
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
chatPeer = peer
|
||||
}
|
||||
var enabled = true
|
||||
if let isPeerEnabled {
|
||||
if let itemPeer {
|
||||
enabled = isPeerEnabled(itemPeer)
|
||||
}
|
||||
} else {
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
enabled = false
|
||||
}
|
||||
if peerEntry.requiresPremiumForMessaging {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer {
|
||||
if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyPrivateChats) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
switch peer {
|
||||
case .user, .secretChat:
|
||||
break
|
||||
default:
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyGroups) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if case .legacyGroup = peer {
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyManageable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
var canManage = false
|
||||
if case let .legacyGroup(peer) = peer {
|
||||
switch peer.role {
|
||||
case .creator, .admin:
|
||||
canManage = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
||||
canManage = true
|
||||
} else if case let .channel(peer) = peer, case .broadcast = peer.info, peer.hasPermission(.addAdmins) {
|
||||
canManage = true
|
||||
}
|
||||
|
||||
if canManage {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if case let .channel(peer) = peer, case .broadcast = peer.info {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let peerContent: ContactsPeerItemPeer = .peer(peer: itemPeer, chatPeer: chatPeer)
|
||||
let status: ContactsPeerItemStatus = .none
|
||||
var header: ChatListSearchItemHeader?
|
||||
switch mode {
|
||||
case let .peers(_, _, additionalCategories, _, _, _, _):
|
||||
if !additionalCategories.isEmpty {
|
||||
let headerType: ChatListSearchItemHeaderType
|
||||
if case .action = additionalCategories[0].appearance {
|
||||
// TODO: hack, generalize
|
||||
headerType = .orImportIntoAnExistingGroup
|
||||
} else {
|
||||
headerType = .chats
|
||||
}
|
||||
header = ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
var status: ContactsPeerItemStatus = .none
|
||||
if isSelecting, let itemPeer = itemPeer {
|
||||
if displayPresence, let presence = presence {
|
||||
status = .presence(presence, presentationData.dateTimeFormat)
|
||||
} else if let (string, multiline, isActive, icon) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters, displayAutoremoveTimeout: displayAutoremoveTimeout, autoremoveTimeout: peerEntry.autoremoveTimeout) {
|
||||
status = .custom(string: string, multiline: multiline, isActive: isActive, icon: icon)
|
||||
} else {
|
||||
status = .none
|
||||
}
|
||||
}
|
||||
|
||||
let peerContent: ContactsPeerItemPeer
|
||||
if let threadInfo = threadInfo, let itemPeer = itemPeer {
|
||||
peerContent = .thread(peer: itemPeer, title: threadInfo.info.title, icon: threadInfo.info.icon, color: threadInfo.info.iconColor)
|
||||
} else {
|
||||
peerContent = .peer(peer: itemPeer, chatPeer: chatPeer)
|
||||
}
|
||||
|
||||
var threadId: Int64?
|
||||
switch index {
|
||||
case let .forum(_, _, threadIdValue, _, _):
|
||||
threadId = threadIdValue
|
||||
case .chatList:
|
||||
break
|
||||
}
|
||||
|
||||
var isForum = false
|
||||
if let peer = chatPeer, case let .channel(channel) = peer, channel.isForumOrMonoForum {
|
||||
isForum = true
|
||||
if editing, case .chatList = mode {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
var selectable = editing
|
||||
if case .chatList = mode {
|
||||
if isForum {
|
||||
selectable = false
|
||||
}
|
||||
}
|
||||
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
|
|
@ -661,69 +643,37 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
peer: peerContent,
|
||||
status: status,
|
||||
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
|
||||
enabled: true,
|
||||
selection: editing ? .selectable(selected: selected) : .none,
|
||||
enabled: enabled,
|
||||
selection: selectable ? .selectable(selected: selected) : .none,
|
||||
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||
index: nil,
|
||||
header: nil,
|
||||
header: header,
|
||||
action: { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
if editing {
|
||||
nodeInteraction.togglePeerSelected(chatPeer, nil)
|
||||
nodeInteraction.togglePeerSelected(chatPeer, threadId)
|
||||
} else {
|
||||
nodeInteraction.peerSelected(chatPeer, nil, nil, nil, false)
|
||||
nodeInteraction.peerSelected(chatPeer, nil, threadId, nil, false)
|
||||
}
|
||||
}
|
||||
}, disabledAction: peerEntry.requiresPremiumForMessaging ? { _ in
|
||||
if let chatPeer {
|
||||
nodeInteraction.disabledPeerSelected(chatPeer, nil, .premiumRequired)
|
||||
}, disabledAction: (isForum && editing) && !peerEntry.requiresPremiumForMessaging ? nil : { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
nodeInteraction.disabledPeerSelected(chatPeer, threadId, peerEntry.requiresPremiumForMessaging ? .premiumRequired : .generic)
|
||||
}
|
||||
} : nil,
|
||||
},
|
||||
animationCache: nodeInteraction.animationCache,
|
||||
animationRenderer: nodeInteraction.animationRenderer
|
||||
), directionHint: entry.directionHint)
|
||||
case .peerType:
|
||||
let itemPeer = peer.chatOrMonoforumMainPeer
|
||||
var chatPeer: EnginePeer?
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
chatPeer = peer
|
||||
}
|
||||
case let .HoleEntry(_, theme):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
||||
case let .GroupReferenceEntry(groupReferenceEntry):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
||||
presentationData: groupReferenceEntry.presentationData,
|
||||
context: context,
|
||||
chatListLocation: location,
|
||||
filterData: filterData,
|
||||
index: groupReferenceEntry.index,
|
||||
content: .groupReference(ChatListItemContent.GroupReferenceData(
|
||||
groupId: groupReferenceEntry.groupId,
|
||||
peers: groupReferenceEntry.peers,
|
||||
message: groupReferenceEntry.message,
|
||||
unreadCount: groupReferenceEntry.unreadCount,
|
||||
hiddenByDefault: groupReferenceEntry.hiddenByDefault,
|
||||
appearsPinned: groupReferenceEntry.appearsPinned,
|
||||
storyState: groupReferenceEntry.storyState.flatMap { storyState in
|
||||
return ChatListItemContent.StoryState(
|
||||
stats: storyState.stats,
|
||||
hasUnseenCloseFriends: storyState.hasUnseenCloseFriends
|
||||
)
|
||||
}
|
||||
)),
|
||||
editing: groupReferenceEntry.editing,
|
||||
hasActiveRevealControls: false,
|
||||
selected: false,
|
||||
header: nil,
|
||||
enabledContextActions: .auto,
|
||||
hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed,
|
||||
interaction: nodeInteraction
|
||||
), directionHint: entry.directionHint)
|
||||
case let .ContactEntry(contactEntry):
|
||||
let header: ChatListSearchItemHeader? = nil
|
||||
|
||||
var status: ContactsPeerItemStatus = .none
|
||||
status = .presence(contactEntry.presence, contactEntry.presentationData.dateTimeFormat)
|
||||
|
||||
let presentationData = contactEntry.presentationData
|
||||
|
||||
let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer)
|
||||
|
||||
let peerContent: ContactsPeerItemPeer = .peer(peer: itemPeer, chatPeer: chatPeer)
|
||||
let status: ContactsPeerItemStatus = .none
|
||||
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
|
|
@ -732,31 +682,103 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
|
||||
enabled: true,
|
||||
selection: .none,
|
||||
selection: editing ? .selectable(selected: selected) : .none,
|
||||
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||
index: nil,
|
||||
header: header,
|
||||
header: nil,
|
||||
action: { _ in
|
||||
nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil, false)
|
||||
},
|
||||
disabledAction: nil,
|
||||
if let chatPeer = chatPeer {
|
||||
if editing {
|
||||
nodeInteraction.togglePeerSelected(chatPeer, nil)
|
||||
} else {
|
||||
nodeInteraction.peerSelected(chatPeer, nil, nil, nil, false)
|
||||
}
|
||||
}
|
||||
}, disabledAction: peerEntry.requiresPremiumForMessaging ? { _ in
|
||||
if let chatPeer {
|
||||
nodeInteraction.disabledPeerSelected(chatPeer, nil, .premiumRequired)
|
||||
}
|
||||
} : nil,
|
||||
animationCache: nodeInteraction.animationCache,
|
||||
animationRenderer: nodeInteraction.animationRenderer
|
||||
), directionHint: entry.directionHint)
|
||||
case let .ArchiveIntro(presentationData):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
||||
case let .EmptyIntro(presentationData):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
||||
case let .SectionHeader(presentationData, displayHide):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
|
||||
hideChatListContacts(context: context)
|
||||
} : nil), directionHint: entry.directionHint)
|
||||
}
|
||||
case let .HoleEntry(_, theme):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
||||
case let .GroupReferenceEntry(groupReferenceEntry):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
||||
presentationData: groupReferenceEntry.presentationData,
|
||||
context: context,
|
||||
chatListLocation: location,
|
||||
filterData: filterData,
|
||||
index: groupReferenceEntry.index,
|
||||
content: .groupReference(ChatListItemContent.GroupReferenceData(
|
||||
groupId: groupReferenceEntry.groupId,
|
||||
peers: groupReferenceEntry.peers,
|
||||
message: groupReferenceEntry.message,
|
||||
unreadCount: groupReferenceEntry.unreadCount,
|
||||
hiddenByDefault: groupReferenceEntry.hiddenByDefault,
|
||||
appearsPinned: groupReferenceEntry.appearsPinned,
|
||||
storyState: groupReferenceEntry.storyState.flatMap { storyState in
|
||||
return ChatListItemContent.StoryState(
|
||||
stats: storyState.stats,
|
||||
hasUnseenCloseFriends: storyState.hasUnseenCloseFriends
|
||||
)
|
||||
}
|
||||
)),
|
||||
editing: groupReferenceEntry.editing,
|
||||
hasActiveRevealControls: false,
|
||||
selected: false,
|
||||
header: nil,
|
||||
enabledContextActions: .auto,
|
||||
hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed,
|
||||
interaction: nodeInteraction
|
||||
), directionHint: entry.directionHint)
|
||||
case let .ContactEntry(contactEntry):
|
||||
let header: ChatListSearchItemHeader? = nil
|
||||
|
||||
var status: ContactsPeerItemStatus = .none
|
||||
status = .presence(contactEntry.presence, contactEntry.presentationData.dateTimeFormat)
|
||||
|
||||
let presentationData = contactEntry.presentationData
|
||||
|
||||
let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer)
|
||||
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
enabled: true,
|
||||
selection: .none,
|
||||
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||
index: nil,
|
||||
header: header,
|
||||
action: { _ in
|
||||
nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil, false)
|
||||
},
|
||||
disabledAction: nil,
|
||||
animationCache: nodeInteraction.animationCache,
|
||||
animationRenderer: nodeInteraction.animationRenderer
|
||||
), directionHint: entry.directionHint)
|
||||
case let .ArchiveIntro(presentationData):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
||||
case let .EmptyIntro(presentationData):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
||||
case let .SectionHeader(presentationData, displayHide):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
|
||||
hideChatListContacts(context: context)
|
||||
} : nil), directionHint: entry.directionHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionUpdateEntry], presentationData: ChatListPresentationData) -> [ListViewUpdateItem] {
|
||||
return entries.map { entry -> ListViewUpdateItem in
|
||||
switch entry.entry {
|
||||
case let .PeerEntry(peerEntry):
|
||||
|
|
@ -828,7 +850,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
hiddenOffset: threadInfo?.isHidden == true && !revealed,
|
||||
interaction: nodeInteraction
|
||||
), directionHint: entry.directionHint)
|
||||
case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence):
|
||||
case let .peers(filter, isSelecting, _, _, filters, displayAutoremoveTimeout, displayPresence):
|
||||
let itemPeer = peer.chatOrMonoforumMainPeer
|
||||
var chatPeer: EnginePeer?
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
|
|
@ -868,7 +890,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
|
||||
var header: ChatListSearchItemHeader?
|
||||
switch mode {
|
||||
case let .peers(_, _, additionalCategories, _, _, _):
|
||||
case let .peers(_, _, additionalCategories, _, _, _, _):
|
||||
if !additionalCategories.isEmpty {
|
||||
let headerType: ChatListSearchItemHeaderType
|
||||
if case .action = additionalCategories[0].appearance {
|
||||
|
|
@ -1066,6 +1088,28 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
} : nil), directionHint: entry.directionHint)
|
||||
case .HeaderEntry:
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
||||
case let .TopPeer(_, peer):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: .peer(peer: peer, chatPeer: peer),
|
||||
status: .none,
|
||||
requiresPremiumForMessaging: false,
|
||||
enabled: true,
|
||||
selection: .none,
|
||||
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||
index: nil,
|
||||
header: nil,
|
||||
action: { _ in
|
||||
nodeInteraction.peerSelected(peer, nil, nil, nil, false)
|
||||
},
|
||||
disabledAction: nil,
|
||||
animationCache: nodeInteraction.animationCache,
|
||||
animationRenderer: nodeInteraction.animationRenderer
|
||||
), directionHint: entry.directionHint)
|
||||
case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData):
|
||||
var header: ChatListSearchItemHeader?
|
||||
if case .peerType = mode {
|
||||
|
|
@ -1091,8 +1135,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
}
|
||||
}
|
||||
|
||||
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
|
||||
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: isPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: isPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade)
|
||||
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, presentationData: ChatListPresentationData, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
|
||||
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: isPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.insertEntries, presentationData: presentationData), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: isPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.updateEntries, presentationData: presentationData), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade)
|
||||
}
|
||||
|
||||
private final class ChatListOpaqueTransactionState {
|
||||
|
|
@ -1196,7 +1240,7 @@ public final class ChatListNode: ListViewImpl {
|
|||
return nil
|
||||
case let .PeerId(value):
|
||||
return PeerId(value)
|
||||
case .ThreadId, .GroupId, .ContactId, .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .additionalCategory:
|
||||
case .ThreadId, .GroupId, .ContactId, .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .additionalCategory, .TopPeer:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1326,7 +1370,7 @@ public final class ChatListNode: ListViewImpl {
|
|||
}
|
||||
|
||||
var isSelecting = false
|
||||
if case .peers(_, true, _, _, _, _) = mode {
|
||||
if case .peers(_, true, _, _, _, _, _) = mode {
|
||||
isSelecting = true
|
||||
}
|
||||
|
||||
|
|
@ -1839,7 +1883,7 @@ public final class ChatListNode: ListViewImpl {
|
|||
let currentRemovingItemId = self.currentRemovingItemId
|
||||
|
||||
let savedMessagesPeer: Signal<EnginePeer?, NoError>
|
||||
if case let .peers(filter, _, _, _, _, _) = mode, filter.contains(.onlyWriteable), case .chatList = location, self.chatListFilter == nil {
|
||||
if case let .peers(filter, _, _, _, _, _, _) = mode, filter.contains(.onlyWriteable), case .chatList = location, self.chatListFilter == nil {
|
||||
savedMessagesPeer = context.account.postbox.loadedPeerWithId(context.account.peerId)
|
||||
|> map(Optional.init)
|
||||
|> map { peer in
|
||||
|
|
@ -2102,7 +2146,7 @@ public final class ChatListNode: ListViewImpl {
|
|||
case .chatList:
|
||||
isEmpty = false
|
||||
return true
|
||||
case let .peers(filter, _, _, _, _, _):
|
||||
case let .peers(filter, _, _, _, _, _, _):
|
||||
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
|
||||
guard !filter.contains(.excludeSavedMessages) || !peer.peerId.isReplies else { return false }
|
||||
guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false }
|
||||
|
|
@ -2538,9 +2582,10 @@ public final class ChatListNode: ListViewImpl {
|
|||
if accountIsPremium != previousAccountIsPremium.swap(accountIsPremium) {
|
||||
forceAllUpdated = true
|
||||
}
|
||||
let presentationData = state.presentationData
|
||||
|
||||
return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode, forceAllUpdated: forceAllUpdated)
|
||||
|> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: accountIsPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, transition: $0) })
|
||||
|> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: accountIsPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, presentationData: presentationData, transition: $0) })
|
||||
|> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue)
|
||||
}
|
||||
|
||||
|
|
@ -3391,7 +3436,7 @@ public final class ChatListNode: ListViewImpl {
|
|||
} else {
|
||||
break loop
|
||||
}
|
||||
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .HeaderEntry, .AdditionalCategory:
|
||||
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .HeaderEntry, .AdditionalCategory, .TopPeer:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ enum ChatListNodeEntryId: Hashable {
|
|||
case SectionHeader
|
||||
case Notice
|
||||
case additionalCategory(Int)
|
||||
case TopPeer(EnginePeer.Id)
|
||||
}
|
||||
|
||||
enum ChatListNodeEntrySortIndex: Comparable {
|
||||
|
|
@ -25,6 +26,7 @@ enum ChatListNodeEntrySortIndex: Comparable {
|
|||
case additionalCategory(Int)
|
||||
case sectionHeader
|
||||
case contact(id: EnginePeer.Id, presence: EnginePeer.Presence)
|
||||
case topPeer(Int)
|
||||
|
||||
static func <(lhs: ChatListNodeEntrySortIndex, rhs: ChatListNodeEntrySortIndex) -> Bool {
|
||||
switch lhs {
|
||||
|
|
@ -34,6 +36,8 @@ enum ChatListNodeEntrySortIndex: Comparable {
|
|||
return lhsIndex < rhsIndex
|
||||
case .additionalCategory:
|
||||
return false
|
||||
case .topPeer:
|
||||
return false
|
||||
case .sectionHeader:
|
||||
return true
|
||||
case .contact:
|
||||
|
|
@ -43,6 +47,8 @@ enum ChatListNodeEntrySortIndex: Comparable {
|
|||
switch rhs {
|
||||
case let .additionalCategory(rhsIndex):
|
||||
return lhsIndex < rhsIndex
|
||||
case .topPeer:
|
||||
return true
|
||||
case .index:
|
||||
return true
|
||||
case .sectionHeader:
|
||||
|
|
@ -50,9 +56,18 @@ enum ChatListNodeEntrySortIndex: Comparable {
|
|||
case .contact:
|
||||
return true
|
||||
}
|
||||
case let .topPeer(lhsIndex):
|
||||
switch rhs {
|
||||
case .additionalCategory:
|
||||
return false
|
||||
case let .topPeer(rhsIndex):
|
||||
return lhsIndex < rhsIndex
|
||||
case .index, .sectionHeader, .contact:
|
||||
return true
|
||||
}
|
||||
case .sectionHeader:
|
||||
switch rhs {
|
||||
case .additionalCategory, .index, .sectionHeader:
|
||||
case .additionalCategory, .topPeer, .index, .sectionHeader:
|
||||
return false
|
||||
case .contact:
|
||||
return true
|
||||
|
|
@ -405,6 +420,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||
case EmptyIntro(presentationData: ChatListPresentationData)
|
||||
case SectionHeader(presentationData: ChatListPresentationData, displayHide: Bool)
|
||||
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
|
||||
case TopPeer(index: Int, peer: EnginePeer)
|
||||
|
||||
var sortIndex: ChatListNodeEntrySortIndex {
|
||||
switch self {
|
||||
|
|
@ -426,6 +442,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||
return .sectionHeader
|
||||
case let .AdditionalCategory(index, _, _, _, _, _, _):
|
||||
return .additionalCategory(index)
|
||||
case let .TopPeer(index, _):
|
||||
return .topPeer(index)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -454,6 +472,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||
return .SectionHeader
|
||||
case let .AdditionalCategory(_, id, _, _, _, _, _):
|
||||
return .additionalCategory(id)
|
||||
case let .TopPeer(_, peer):
|
||||
return .TopPeer(peer.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -551,6 +571,12 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .TopPeer(index, peer):
|
||||
if case .TopPeer(index, peer) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -930,12 +956,18 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
|
|||
}
|
||||
|
||||
if !view.hasLater {
|
||||
if case let .peers(_, _, additionalCategories, _, _, _) = mode {
|
||||
if case let .peers(_, _, additionalCategories, topPeers, _, _, _) = mode {
|
||||
var index = 0
|
||||
for category in additionalCategories.reversed() {
|
||||
result.append(.AdditionalCategory(index: index, id: category.id, title: category.title, image: category.icon, appearance: category.appearance, selected: state.selectedAdditionalCategoryIds.contains(category.id), presentationData: state.presentationData))
|
||||
index += 1
|
||||
}
|
||||
|
||||
index = 0
|
||||
for topPeer in topPeers {
|
||||
result.append(.TopPeer(index: index, peer: topPeer))
|
||||
index += 1
|
||||
}
|
||||
} else if case let .peerType(types, hasCreate) = mode, !result.isEmpty && hasCreate {
|
||||
for type in types {
|
||||
switch type {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||
private let filter: ChatListNodePeersFilter
|
||||
private let forumPeerId: (id: EnginePeer.Id, isMonoforum: Bool)?
|
||||
private let selectForumThreads: Bool
|
||||
private let suggestedPeers: [EnginePeer]
|
||||
|
||||
private let attemptSelection: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
|
||||
private let createNewGroup: (() -> Void)?
|
||||
|
|
@ -114,6 +115,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||
self.immediatelySwitchToContacts = params.immediatelySwitchToContacts
|
||||
self.immediatelyActivateMultipleSelection = params.immediatelyActivateMultipleSelection
|
||||
self.multipleSelectionLimit = params.multipleSelectionLimit
|
||||
self.suggestedPeers = params.suggestedPeers
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
|
||||
|
||||
|
|
@ -263,7 +265,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||
override public func loadDisplayNode() {
|
||||
self.navigationBar?.secondaryContentHeight = 44.0 + 10.0
|
||||
|
||||
self.displayNode = PeerSelectionControllerNode(context: self.context, controller: self, presentationData: self.presentationData, filter: self.filter, forumPeerId: self.forumPeerId, hasFilters: self.hasFilters, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, hasTypeHeaders: self.hasTypeHeaders, requestPeerType: self.requestPeerType, hasCreation: self.hasCreation, createNewGroup: self.createNewGroup, present: { [weak self] c, a in
|
||||
self.displayNode = PeerSelectionControllerNode(context: self.context, controller: self, presentationData: self.presentationData, filter: self.filter, forumPeerId: self.forumPeerId, hasFilters: self.hasFilters, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, hasTypeHeaders: self.hasTypeHeaders, requestPeerType: self.requestPeerType, hasCreation: self.hasCreation, createNewGroup: self.createNewGroup, suggestedPeers: self.suggestedPeers, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}, presentInGlobalOverlay: { [weak self] c, a in
|
||||
self?.presentInGlobalOverlay(c, with: a)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
private let forwardedMessageIds: [EngineMessage.Id]
|
||||
private let hasTypeHeaders: Bool
|
||||
private let requestPeerType: [ReplyMarkupButtonRequestPeerType]?
|
||||
private let suggestedPeers: [EnginePeer]
|
||||
|
||||
private var presentationInterfaceState: ChatPresentationInterfaceState
|
||||
private let presentationInterfaceStatePromise = ValuePromise<ChatPresentationInterfaceState>()
|
||||
|
|
@ -115,7 +116,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
return (self.presentationData, self.presentationDataPromise.get())
|
||||
}
|
||||
|
||||
init(context: AccountContext, controller: PeerSelectionControllerImpl, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: (id: EnginePeer.Id, isMonoforum: Bool)?, hasFilters: Bool, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, hasCreation: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
|
||||
init(context: AccountContext, controller: PeerSelectionControllerImpl, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: (id: EnginePeer.Id, isMonoforum: Bool)?, hasFilters: Bool, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, hasCreation: Bool, createNewGroup: (() -> Void)?, suggestedPeers: [EnginePeer], present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.controller = controller
|
||||
self.present = present
|
||||
|
|
@ -127,6 +128,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
self.forwardedMessageIds = forwardedMessageIds
|
||||
self.hasTypeHeaders = hasTypeHeaders
|
||||
self.requestPeerType = requestPeerType
|
||||
self.suggestedPeers = suggestedPeers
|
||||
|
||||
self.presentationData = presentationData
|
||||
|
||||
|
|
@ -209,7 +211,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
if let requestPeerType = self.requestPeerType {
|
||||
chatListMode = .peerType(type: requestPeerType, hasCreate: hasCreation)
|
||||
} else {
|
||||
chatListMode = .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil, displayAutoremoveTimeout: false, displayPresence: false)
|
||||
chatListMode = .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, topPeers: self.suggestedPeers, chatListFilters: nil, displayAutoremoveTimeout: false, displayPresence: false)
|
||||
}
|
||||
|
||||
if hasFilters {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ swift_library(
|
|||
"//submodules/SegmentedControlNode",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/HorizontalTabsComponent",
|
||||
"//submodules/TelegramUI/Components/LiquidLens",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
],
|
||||
visibility = [
|
||||
|
|
|
|||
|
|
@ -704,43 +704,46 @@ final class StorageFileListPanelComponent: Component {
|
|||
private struct ItemLayout: Equatable {
|
||||
let containerInsets: UIEdgeInsets
|
||||
let containerWidth: CGFloat
|
||||
let sideInset: CGFloat
|
||||
let itemHeight: CGFloat
|
||||
let itemCount: Int
|
||||
|
||||
|
||||
let contentHeight: CGFloat
|
||||
|
||||
|
||||
init(
|
||||
containerInsets: UIEdgeInsets,
|
||||
containerWidth: CGFloat,
|
||||
sideInset: CGFloat,
|
||||
itemHeight: CGFloat,
|
||||
itemCount: Int
|
||||
) {
|
||||
self.containerInsets = containerInsets
|
||||
self.containerWidth = containerWidth
|
||||
self.sideInset = sideInset
|
||||
self.itemHeight = itemHeight
|
||||
self.itemCount = itemCount
|
||||
|
||||
|
||||
self.contentHeight = containerInsets.top + containerInsets.bottom + CGFloat(itemCount) * itemHeight
|
||||
}
|
||||
|
||||
|
||||
func visibleItems(for rect: CGRect) -> Range<Int>? {
|
||||
let offsetRect = rect.offsetBy(dx: -self.containerInsets.left, dy: -self.containerInsets.top)
|
||||
var minVisibleRow = Int(floor((offsetRect.minY) / (self.itemHeight)))
|
||||
minVisibleRow = max(0, minVisibleRow)
|
||||
let maxVisibleRow = Int(ceil((offsetRect.maxY) / (self.itemHeight)))
|
||||
|
||||
|
||||
let minVisibleIndex = minVisibleRow
|
||||
let maxVisibleIndex = maxVisibleRow
|
||||
|
||||
|
||||
if maxVisibleIndex >= minVisibleIndex {
|
||||
return minVisibleIndex ..< (maxVisibleIndex + 1)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func itemFrame(for index: Int) -> CGRect {
|
||||
return CGRect(origin: CGPoint(x: 0.0, y: self.containerInsets.top + CGFloat(index) * self.itemHeight), size: CGSize(width: self.containerWidth, height: self.itemHeight))
|
||||
return CGRect(origin: CGPoint(x: self.sideInset, y: self.containerInsets.top + CGFloat(index) * self.itemHeight), size: CGSize(width: self.containerWidth - self.sideInset * 2.0, height: self.itemHeight))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -752,37 +755,48 @@ final class StorageFileListPanelComponent: Component {
|
|||
|
||||
class View: UIView, UIScrollViewDelegate {
|
||||
private let scrollView: ScrollViewImpl
|
||||
|
||||
private let listBackgroundView: UIImageView
|
||||
private let listMaskView: UIImageView
|
||||
|
||||
private let measureItem = ComponentView<Empty>()
|
||||
private var visibleItems: [EngineMessage.Id: ComponentView<Empty>] = [:]
|
||||
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
|
||||
private var component: StorageFileListPanelComponent?
|
||||
private var environment: StorageUsagePanelEnvironment?
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollView = ScrollViewImpl()
|
||||
|
||||
self.listBackgroundView = UIImageView()
|
||||
self.listBackgroundView.image = generateStretchableFilledCircleImage(diameter: 26.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.listMaskView = UIImageView()
|
||||
self.listMaskView.image = generateImage(CGSize(width: 16.0 + 26.0 * 2.0 + 16.0, height: 26.0 * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16 + 26, topCapHeight: 26).withRenderingMode(.alwaysTemplate)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
|
||||
self.addSubview(self.listBackgroundView)
|
||||
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
if #available(iOS 13.0, *) {
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
}
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
self.scrollView.clipsToBounds = true
|
||||
self.addSubview(self.scrollView)
|
||||
self.addSubview(self.listMaskView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -792,13 +806,44 @@ final class StorageFileListPanelComponent: Component {
|
|||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateScrolling(transition: .immediate)
|
||||
self.updateListBackground(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
cancelContextGestures(view: scrollView)
|
||||
}
|
||||
|
||||
|
||||
private func updateListBackground(transition: ComponentTransition) {
|
||||
guard let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
let size = CGSize(width: itemLayout.containerWidth, height: self.scrollView.bounds.height)
|
||||
guard size.width != 0.0 else {
|
||||
return
|
||||
}
|
||||
|
||||
let topInset = itemLayout.containerInsets.top
|
||||
|
||||
var distanceToTop: CGFloat = -100.0
|
||||
var distanceToBottom: CGFloat = -100.0
|
||||
|
||||
let scrollingOffset = self.scrollView.contentOffset.y
|
||||
distanceToTop = -scrollingOffset + topInset
|
||||
|
||||
let itemsContentHeight = self.scrollView.contentSize.height - itemLayout.containerInsets.bottom
|
||||
let contentBottomOffset = itemsContentHeight - scrollingOffset - self.scrollView.bounds.height
|
||||
distanceToBottom = -contentBottomOffset
|
||||
|
||||
distanceToTop = max(-100.0, distanceToTop)
|
||||
distanceToBottom = max(-100.0, distanceToBottom)
|
||||
|
||||
let listBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: distanceToTop), size: CGSize(width: max(1.0, size.width - 16.0 * 2.0), height: max(1.0, size.height - distanceToBottom - distanceToTop)))
|
||||
let listMaskFrame = CGRect(origin: CGPoint(x: 0.0, y: listBackgroundFrame.minY), size: CGSize(width: listBackgroundFrame.width + 16.0 * 2.0, height: listBackgroundFrame.height))
|
||||
transition.setFrame(view: self.listBackgroundView, frame: listBackgroundFrame)
|
||||
transition.setFrame(view: self.listMaskView, frame: listMaskFrame)
|
||||
}
|
||||
|
||||
private func updateScrolling(transition: ComponentTransition) {
|
||||
guard let component = self.component, let environment = self.environment, let items = component.items, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
|
|
@ -975,7 +1020,7 @@ final class StorageFileListPanelComponent: Component {
|
|||
contextAction: component.contextAction
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight)
|
||||
containerSize: CGSize(width: itemLayout.containerWidth - itemLayout.sideInset * 2.0, height: itemLayout.itemHeight)
|
||||
)
|
||||
let itemFrame = itemLayout.itemFrame(for: index)
|
||||
if let itemComponentView = itemView.view {
|
||||
|
|
@ -1008,6 +1053,9 @@ final class StorageFileListPanelComponent: Component {
|
|||
|
||||
let environment = environment[StorageUsagePanelEnvironment.self].value
|
||||
self.environment = environment
|
||||
|
||||
self.listBackgroundView.tintColor = environment.theme.list.itemBlocksBackgroundColor
|
||||
self.listMaskView.tintColor = environment.theme.list.blocksBackgroundColor
|
||||
|
||||
let measureItemSize = self.measureItem.update(
|
||||
transition: .immediate,
|
||||
|
|
@ -1034,6 +1082,7 @@ final class StorageFileListPanelComponent: Component {
|
|||
let itemLayout = ItemLayout(
|
||||
containerInsets: environment.containerInsets,
|
||||
containerWidth: availableSize.width,
|
||||
sideInset: 16.0,
|
||||
itemHeight: measureItemSize.height,
|
||||
itemCount: component.items?.items.count ?? 0
|
||||
)
|
||||
|
|
@ -1060,7 +1109,8 @@ final class StorageFileListPanelComponent: Component {
|
|||
}
|
||||
self.ignoreScrolling = false
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
self.updateListBackground(transition: transition)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,43 +454,46 @@ final class StoragePeerListPanelComponent: Component {
|
|||
private struct ItemLayout: Equatable {
|
||||
let containerInsets: UIEdgeInsets
|
||||
let containerWidth: CGFloat
|
||||
let sideInset: CGFloat
|
||||
let itemHeight: CGFloat
|
||||
let itemCount: Int
|
||||
|
||||
|
||||
let contentHeight: CGFloat
|
||||
|
||||
|
||||
init(
|
||||
containerInsets: UIEdgeInsets,
|
||||
containerWidth: CGFloat,
|
||||
sideInset: CGFloat,
|
||||
itemHeight: CGFloat,
|
||||
itemCount: Int
|
||||
) {
|
||||
self.containerInsets = containerInsets
|
||||
self.containerWidth = containerWidth
|
||||
self.sideInset = sideInset
|
||||
self.itemHeight = itemHeight
|
||||
self.itemCount = itemCount
|
||||
|
||||
|
||||
self.contentHeight = containerInsets.top + containerInsets.bottom + CGFloat(itemCount) * itemHeight
|
||||
}
|
||||
|
||||
|
||||
func visibleItems(for rect: CGRect) -> Range<Int>? {
|
||||
let offsetRect = rect.offsetBy(dx: -self.containerInsets.left, dy: -self.containerInsets.top)
|
||||
var minVisibleRow = Int(floor((offsetRect.minY) / (self.itemHeight)))
|
||||
minVisibleRow = max(0, minVisibleRow)
|
||||
let maxVisibleRow = Int(ceil((offsetRect.maxY) / (self.itemHeight)))
|
||||
|
||||
|
||||
let minVisibleIndex = minVisibleRow
|
||||
let maxVisibleIndex = maxVisibleRow
|
||||
|
||||
|
||||
if maxVisibleIndex >= minVisibleIndex {
|
||||
return minVisibleIndex ..< (maxVisibleIndex + 1)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func itemFrame(for index: Int) -> CGRect {
|
||||
return CGRect(origin: CGPoint(x: 0.0, y: self.containerInsets.top + CGFloat(index) * self.itemHeight), size: CGSize(width: self.containerWidth, height: self.itemHeight))
|
||||
return CGRect(origin: CGPoint(x: self.sideInset, y: self.containerInsets.top + CGFloat(index) * self.itemHeight), size: CGSize(width: self.containerWidth - self.sideInset * 2.0, height: self.itemHeight))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -502,7 +505,9 @@ final class StoragePeerListPanelComponent: Component {
|
|||
|
||||
class View: UIView, UIScrollViewDelegate {
|
||||
private let scrollView: ScrollViewImpl
|
||||
|
||||
private let listBackgroundView: UIImageView
|
||||
private let listMaskView: UIImageView
|
||||
|
||||
private let measureItem = ComponentView<Empty>()
|
||||
private var visibleItems: [EnginePeer.Id: ComponentView<Empty>] = [:]
|
||||
|
||||
|
|
@ -514,25 +519,35 @@ final class StoragePeerListPanelComponent: Component {
|
|||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollView = ScrollViewImpl()
|
||||
|
||||
|
||||
self.listBackgroundView = UIImageView()
|
||||
self.listBackgroundView.image = generateStretchableFilledCircleImage(diameter: 26.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.listMaskView = UIImageView()
|
||||
self.listMaskView.image = generateImage(CGSize(width: 16.0 + 26.0 * 2.0 + 16.0, height: 26.0 * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16 + 26, topCapHeight: 26).withRenderingMode(.alwaysTemplate)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
|
||||
self.addSubview(self.listBackgroundView)
|
||||
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
if #available(iOS 13.0, *) {
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
}
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
self.scrollView.clipsToBounds = true
|
||||
self.addSubview(self.scrollView)
|
||||
self.addSubview(self.listMaskView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -542,13 +557,44 @@ final class StoragePeerListPanelComponent: Component {
|
|||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateScrolling(transition: .immediate)
|
||||
self.updateListBackground(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
cancelContextGestures(view: scrollView)
|
||||
}
|
||||
|
||||
|
||||
private func updateListBackground(transition: ComponentTransition) {
|
||||
guard let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
let size = CGSize(width: itemLayout.containerWidth, height: self.scrollView.bounds.height)
|
||||
guard size.width != 0.0 else {
|
||||
return
|
||||
}
|
||||
|
||||
let topInset = itemLayout.containerInsets.top
|
||||
|
||||
var distanceToTop: CGFloat = -100.0
|
||||
var distanceToBottom: CGFloat = -100.0
|
||||
|
||||
let scrollingOffset = self.scrollView.contentOffset.y
|
||||
distanceToTop = -scrollingOffset + topInset
|
||||
|
||||
let itemsContentHeight = self.scrollView.contentSize.height - itemLayout.containerInsets.bottom
|
||||
let contentBottomOffset = itemsContentHeight - scrollingOffset - self.scrollView.bounds.height
|
||||
distanceToBottom = -contentBottomOffset
|
||||
|
||||
distanceToTop = max(-100.0, distanceToTop)
|
||||
distanceToBottom = max(-100.0, distanceToBottom)
|
||||
|
||||
let listBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: distanceToTop), size: CGSize(width: max(1.0, size.width - 16.0 * 2.0), height: max(1.0, size.height - distanceToBottom - distanceToTop)))
|
||||
let listMaskFrame = CGRect(origin: CGPoint(x: 0.0, y: listBackgroundFrame.minY), size: CGSize(width: listBackgroundFrame.width + 16.0 * 2.0, height: listBackgroundFrame.height))
|
||||
transition.setFrame(view: self.listBackgroundView, frame: listBackgroundFrame)
|
||||
transition.setFrame(view: self.listMaskView, frame: listMaskFrame)
|
||||
}
|
||||
|
||||
private func updateScrolling(transition: ComponentTransition) {
|
||||
guard let component = self.component, let environment = self.environment, let items = component.items, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
|
|
@ -607,7 +653,7 @@ final class StoragePeerListPanelComponent: Component {
|
|||
contextAction: component.contextAction
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight)
|
||||
containerSize: CGSize(width: itemLayout.containerWidth - itemLayout.sideInset * 2.0, height: itemLayout.itemHeight)
|
||||
)
|
||||
let itemFrame = itemLayout.itemFrame(for: index)
|
||||
if let itemComponentView = itemView.view {
|
||||
|
|
@ -640,6 +686,9 @@ final class StoragePeerListPanelComponent: Component {
|
|||
|
||||
let environment = environment[StorageUsagePanelEnvironment.self].value
|
||||
self.environment = environment
|
||||
|
||||
self.listBackgroundView.tintColor = environment.theme.list.itemBlocksBackgroundColor
|
||||
self.listMaskView.tintColor = environment.theme.list.blocksBackgroundColor
|
||||
|
||||
let measureItemSize = self.measureItem.update(
|
||||
transition: .immediate,
|
||||
|
|
@ -664,6 +713,7 @@ final class StoragePeerListPanelComponent: Component {
|
|||
let itemLayout = ItemLayout(
|
||||
containerInsets: environment.containerInsets,
|
||||
containerWidth: availableSize.width,
|
||||
sideInset: 16.0,
|
||||
itemHeight: measureItemSize.height,
|
||||
itemCount: component.items?.items.count ?? 0
|
||||
)
|
||||
|
|
@ -690,7 +740,8 @@ final class StoragePeerListPanelComponent: Component {
|
|||
}
|
||||
self.ignoreScrolling = false
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
self.updateListBackground(transition: transition)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import Display
|
|||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import HorizontalTabsComponent
|
||||
import GlassBackgroundComponent
|
||||
|
||||
final class StorageUsagePanelContainerEnvironment: Equatable {
|
||||
let isScrollable: Bool
|
||||
|
|
@ -63,264 +65,6 @@ final class StorageUsagePanelEnvironment: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
private final class StorageUsageHeaderItemComponent: CombinedComponent {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let activityFraction: CGFloat
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
activityFraction: CGFloat
|
||||
) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.activityFraction = activityFraction
|
||||
}
|
||||
|
||||
static func ==(lhs: StorageUsageHeaderItemComponent, rhs: StorageUsageHeaderItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.activityFraction != rhs.activityFraction {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let activeText = Child(Text.self)
|
||||
let inactiveText = Child(Text.self)
|
||||
|
||||
return { context in
|
||||
let activeText = activeText.update(
|
||||
component: Text(text: context.component.title, font: Font.medium(14.0), color: context.component.theme.list.itemAccentColor),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
let inactiveText = inactiveText.update(
|
||||
component: Text(text: context.component.title, font: Font.medium(14.0), color: context.component.theme.list.itemSecondaryTextColor),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
context.add(activeText
|
||||
.position(CGPoint(x: activeText.size.width * 0.5, y: activeText.size.height * 0.5))
|
||||
.opacity(context.component.activityFraction)
|
||||
)
|
||||
context.add(inactiveText
|
||||
.position(CGPoint(x: inactiveText.size.width * 0.5, y: inactiveText.size.height * 0.5))
|
||||
.opacity(1.0 - context.component.activityFraction)
|
||||
)
|
||||
|
||||
return activeText.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class StorageUsageHeaderComponent: Component {
|
||||
struct Item: Equatable {
|
||||
let id: AnyHashable
|
||||
let title: String
|
||||
|
||||
init(
|
||||
id: AnyHashable,
|
||||
title: String
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
}
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let items: [Item]
|
||||
let activeIndex: Int
|
||||
let transitionFraction: CGFloat
|
||||
let switchToPanel: (AnyHashable) -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
items: [Item],
|
||||
activeIndex: Int,
|
||||
transitionFraction: CGFloat,
|
||||
switchToPanel: @escaping (AnyHashable) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.items = items
|
||||
self.activeIndex = activeIndex
|
||||
self.transitionFraction = transitionFraction
|
||||
self.switchToPanel = switchToPanel
|
||||
}
|
||||
|
||||
static func ==(lhs: StorageUsageHeaderComponent, rhs: StorageUsageHeaderComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
if lhs.activeIndex != rhs.activeIndex {
|
||||
return false
|
||||
}
|
||||
if lhs.transitionFraction != rhs.transitionFraction {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class View: UIView {
|
||||
private var component: StorageUsageHeaderComponent?
|
||||
|
||||
private var visibleItems: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private let activeItemLayer: SimpleLayer
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.activeItemLayer = SimpleLayer()
|
||||
self.activeItemLayer.cornerRadius = 2.0
|
||||
self.activeItemLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(self.activeItemLayer)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
let point = recognizer.location(in: self)
|
||||
var closestId: (CGFloat, AnyHashable)?
|
||||
if self.bounds.contains(point) {
|
||||
for (id, item) in self.visibleItems {
|
||||
if let itemView = item.view {
|
||||
let distance: CGFloat = min(abs(point.x - itemView.frame.minX), abs(point.x - itemView.frame.maxX))
|
||||
if let closestIdValue = closestId {
|
||||
if distance < closestIdValue.0 {
|
||||
closestId = (distance, id)
|
||||
}
|
||||
} else {
|
||||
closestId = (distance, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let closestId = closestId, let component = self.component {
|
||||
component.switchToPanel(closestId.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: StorageUsageHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let themeUpdated = self.component?.theme !== component.theme
|
||||
|
||||
self.component = component
|
||||
|
||||
var validIds = Set<AnyHashable>()
|
||||
for i in 0 ..< component.items.count {
|
||||
let item = component.items[i]
|
||||
validIds.insert(item.id)
|
||||
|
||||
let itemView: ComponentView<Empty>
|
||||
var itemTransition = transition
|
||||
if let current = self.visibleItems[item.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = .immediate
|
||||
itemView = ComponentView()
|
||||
self.visibleItems[item.id] = itemView
|
||||
}
|
||||
|
||||
let activeIndex: CGFloat = CGFloat(component.activeIndex) - component.transitionFraction
|
||||
let activityDistance: CGFloat = abs(activeIndex - CGFloat(i))
|
||||
|
||||
let activityFraction: CGFloat
|
||||
if activityDistance < 1.0 {
|
||||
activityFraction = 1.0 - activityDistance
|
||||
} else {
|
||||
activityFraction = 0.0
|
||||
}
|
||||
|
||||
let itemSize = itemView.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(StorageUsageHeaderItemComponent(
|
||||
theme: component.theme,
|
||||
title: item.title,
|
||||
activityFraction: activityFraction
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let itemHorizontalSpace = availableSize.width / CGFloat(component.items.count)
|
||||
let itemX: CGFloat
|
||||
if component.items.count == 1 {
|
||||
itemX = 37.0
|
||||
} else {
|
||||
itemX = itemHorizontalSpace * CGFloat(i) + floor((itemHorizontalSpace - itemSize.width) / 2.0)
|
||||
}
|
||||
|
||||
let itemFrame = CGRect(origin: CGPoint(x: itemX, y: floor((availableSize.height - itemSize.height) / 2.0)), size: itemSize)
|
||||
if let itemComponentView = itemView.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.addSubview(itemComponentView)
|
||||
itemComponentView.isUserInteractionEnabled = false
|
||||
}
|
||||
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||
}
|
||||
}
|
||||
|
||||
if component.activeIndex < component.items.count {
|
||||
let activeView = self.visibleItems[component.items[component.activeIndex].id]?.view
|
||||
let nextIndex: Int
|
||||
if component.transitionFraction > 0.0 {
|
||||
nextIndex = max(0, component.activeIndex - 1)
|
||||
} else {
|
||||
nextIndex = min(component.items.count - 1, component.activeIndex + 1)
|
||||
}
|
||||
let nextView = self.visibleItems[component.items[nextIndex].id]?.view
|
||||
if let activeView = activeView, let nextView = nextView {
|
||||
let mergedFrame = activeView.frame.interpolate(to: nextView.frame, amount: abs(component.transitionFraction))
|
||||
transition.setFrame(layer: self.activeItemLayer, frame: CGRect(origin: CGPoint(x: mergedFrame.minX, y: availableSize.height - 3.0), size: CGSize(width: mergedFrame.width, height: 3.0)))
|
||||
}
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
self.activeItemLayer.backgroundColor = component.theme.list.itemAccentColor.cgColor
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.visibleItems {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
if let itemComponentView = itemView.view {
|
||||
itemComponentView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.visibleItems.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class StorageUsagePanelContainerComponent: Component {
|
||||
typealias EnvironmentType = StorageUsagePanelContainerEnvironment
|
||||
|
||||
|
|
@ -383,10 +127,9 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
}
|
||||
|
||||
class View: UIView, UIGestureRecognizerDelegate {
|
||||
private let topPanelBackgroundView: UIView
|
||||
private let topPanelMergedBackgroundView: UIView
|
||||
private let topPanelSeparatorLayer: SimpleLayer
|
||||
private let header = ComponentView<Empty>()
|
||||
private let tabsBackgroundContainer: GlassBackgroundContainerView
|
||||
private let tabsBackgroundView: GlassBackgroundView
|
||||
private let tabsContainer = ComponentView<Empty>()
|
||||
|
||||
private var component: StorageUsagePanelContainerComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
|
@ -396,25 +139,21 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
private var actualVisibleIds = Set<AnyHashable>()
|
||||
private var currentId: AnyHashable?
|
||||
private var transitionFraction: CGFloat = 0.0
|
||||
private var isDraggingTabs: Bool = false
|
||||
private var animatingTransition: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.topPanelBackgroundView = UIView()
|
||||
|
||||
self.topPanelMergedBackgroundView = UIView()
|
||||
self.topPanelMergedBackgroundView.alpha = 0.0
|
||||
|
||||
self.topPanelSeparatorLayer = SimpleLayer()
|
||||
|
||||
self.tabsBackgroundContainer = GlassBackgroundContainerView()
|
||||
self.tabsBackgroundView = GlassBackgroundView()
|
||||
|
||||
self.panelsBackgroundLayer = SimpleLayer()
|
||||
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
|
||||
self.layer.addSublayer(self.panelsBackgroundLayer)
|
||||
self.addSubview(self.topPanelBackgroundView)
|
||||
self.addSubview(self.topPanelMergedBackgroundView)
|
||||
self.layer.addSublayer(self.topPanelSeparatorLayer)
|
||||
|
||||
self.tabsBackgroundContainer.contentView.addSubview(self.tabsBackgroundView)
|
||||
self.addSubview(self.tabsBackgroundContainer)
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
|
||||
guard let self, let component = self.component, let currentId = self.currentId else {
|
||||
return []
|
||||
|
|
@ -422,11 +161,7 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
guard let index = component.items.firstIndex(where: { $0.id == currentId }) else {
|
||||
return []
|
||||
}
|
||||
|
||||
/*if strongSelf.tabsContainerNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.tabsContainerNode.view)) {
|
||||
return []
|
||||
}*/
|
||||
|
||||
|
||||
if index == 0 {
|
||||
return .left
|
||||
}
|
||||
|
|
@ -480,7 +215,8 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
}
|
||||
|
||||
cancelContextGestures(view: self)
|
||||
|
||||
self.isDraggingTabs = true
|
||||
|
||||
//self.animatingTransition = true
|
||||
case .changed:
|
||||
guard let component = self.component, let currentId = self.currentId else {
|
||||
|
|
@ -535,6 +271,7 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
component.currentPanelUpdated(currentId, transition)
|
||||
}
|
||||
|
||||
self.isDraggingTabs = false
|
||||
self.animatingTransition = false
|
||||
//self.currentPaneUpdated?(false)
|
||||
|
||||
|
|
@ -545,8 +282,16 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
}
|
||||
|
||||
func updateNavigationMergeFactor(value: CGFloat, transition: ComponentTransition) {
|
||||
transition.setAlpha(view: self.topPanelMergedBackgroundView, alpha: value)
|
||||
transition.setAlpha(view: self.topPanelBackgroundView, alpha: 1.0 - value)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard let component = self.component else {
|
||||
return nil
|
||||
}
|
||||
if point.y < component.insets.top {
|
||||
return nil
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func update(component: StorageUsagePanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StorageUsagePanelContainerEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
|
|
@ -558,34 +303,30 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
self.state = state
|
||||
|
||||
if themeUpdated {
|
||||
self.panelsBackgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor
|
||||
self.topPanelSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
self.topPanelBackgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
||||
self.topPanelMergedBackgroundView.backgroundColor = component.theme.list.blocksBackgroundColor
|
||||
//self.panelsBackgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor
|
||||
}
|
||||
|
||||
let topPanelCoverHeight: CGFloat = 10.0
|
||||
|
||||
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -topPanelCoverHeight), size: CGSize(width: availableSize.width, height: 44.0))
|
||||
transition.setFrame(view: self.topPanelBackgroundView, frame: topPanelFrame)
|
||||
transition.setFrame(view: self.topPanelMergedBackgroundView, frame: topPanelFrame)
|
||||
|
||||
transition.setFrame(layer: self.panelsBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY)))
|
||||
|
||||
transition.setFrame(layer: self.topPanelSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
|
||||
let tabsHeight: CGFloat = 40.0
|
||||
let tabsTopInset: CGFloat = component.insets.top + 10.0
|
||||
let tabsBottomInset: CGFloat = 10.0
|
||||
let tabsSideInset: CGFloat = 16.0 + component.insets.left
|
||||
|
||||
let tabsContainerSize = CGSize(width: availableSize.width - tabsSideInset * 2.0, height: tabsHeight)
|
||||
|
||||
transition.setFrame(layer: self.panelsBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: component.insets.top), size: CGSize(width: availableSize.width, height: availableSize.height - component.insets.top)))
|
||||
|
||||
if let currentIdValue = self.currentId, !component.items.contains(where: { $0.id == currentIdValue }) {
|
||||
self.currentId = nil
|
||||
}
|
||||
if self.currentId == nil {
|
||||
self.currentId = component.items.first?.id
|
||||
}
|
||||
|
||||
|
||||
var visibleIds = Set<AnyHashable>()
|
||||
var currentIndex: Int?
|
||||
if let currentId = self.currentId {
|
||||
visibleIds.insert(currentId)
|
||||
|
||||
|
||||
if let index = component.items.firstIndex(where: { $0.id == currentId }) {
|
||||
currentIndex = index
|
||||
if index != 0 {
|
||||
|
|
@ -596,50 +337,73 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.header.update(
|
||||
|
||||
let tabsContainerEffectiveSize = self.tabsContainer.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(StorageUsageHeaderComponent(
|
||||
component: AnyComponent(HorizontalTabsComponent(
|
||||
context: nil,
|
||||
theme: component.theme,
|
||||
items: component.items.map { item -> StorageUsageHeaderComponent.Item in
|
||||
return StorageUsageHeaderComponent.Item(
|
||||
tabs: component.items.map { item -> HorizontalTabsComponent.Tab in
|
||||
return HorizontalTabsComponent.Tab(
|
||||
id: item.id,
|
||||
title: item.title
|
||||
content: .title(HorizontalTabsComponent.Tab.Title(text: item.title, entities: [], enableAnimations: false)),
|
||||
badge: nil,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if component.items.contains(where: { $0.id == item.id }) {
|
||||
self.currentId = item.id
|
||||
let transition = ComponentTransition(animation: .curve(duration: 0.35, curve: .spring))
|
||||
self.state?.updated(transition: transition)
|
||||
component.currentPanelUpdated(item.id, transition)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
activeIndex: currentIndex ?? 0,
|
||||
transitionFraction: self.transitionFraction,
|
||||
switchToPanel: { [weak self] id in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if component.items.contains(where: { $0.id == id }) {
|
||||
self.currentId = id
|
||||
let transition = ComponentTransition(animation: .curve(duration: 0.35, curve: .spring))
|
||||
self.state?.updated(transition: transition)
|
||||
component.currentPanelUpdated(id, transition)
|
||||
}
|
||||
}
|
||||
selectedTab: self.currentId,
|
||||
isEditing: false,
|
||||
layout: .fit,
|
||||
liftWhileSwitching: true
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: topPanelFrame.size
|
||||
containerSize: tabsContainerSize
|
||||
)
|
||||
if let headerView = self.header.view {
|
||||
if headerView.superview == nil {
|
||||
self.addSubview(headerView)
|
||||
|
||||
let tabContainerFrame = CGRect(
|
||||
origin: CGPoint(
|
||||
x: floorToScreenPixels((availableSize.width - tabsContainerEffectiveSize.width) / 2.0),
|
||||
y: tabsTopInset
|
||||
),
|
||||
size: tabsContainerEffectiveSize
|
||||
)
|
||||
|
||||
transition.setFrame(view: self.tabsBackgroundContainer, frame: tabContainerFrame)
|
||||
self.tabsBackgroundContainer.update(size: tabContainerFrame.size, isDark: component.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
transition.setFrame(view: self.tabsBackgroundView, frame: CGRect(origin: CGPoint(), size: tabContainerFrame.size))
|
||||
self.tabsBackgroundView.update(size: tabContainerFrame.size, cornerRadius: tabContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: transition)
|
||||
|
||||
if let tabsContainerView = self.tabsContainer.view as? HorizontalTabsComponent.View {
|
||||
if tabsContainerView.superview == nil {
|
||||
self.tabsBackgroundView.contentView.addSubview(tabsContainerView)
|
||||
tabsContainerView.setOverlayContainerView(overlayContainerView: self)
|
||||
}
|
||||
transition.setFrame(view: headerView, frame: topPanelFrame)
|
||||
transition.setFrame(view: tabsContainerView, frame: CGRect(origin: CGPoint(), size: tabContainerFrame.size))
|
||||
tabsContainerView.updateTabSwitchFraction(fraction: self.transitionFraction, isDragging: self.isDraggingTabs, transition: transition)
|
||||
}
|
||||
|
||||
let effectiveTabsHeight = tabsTopInset + tabContainerFrame.height + tabsBottomInset
|
||||
|
||||
let childEnvironment = StorageUsagePanelEnvironment(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
dateTimeFormat: component.dateTimeFormat,
|
||||
containerInsets: UIEdgeInsets(top: 0.0, left: component.insets.left, bottom: component.insets.bottom, right: component.insets.right),
|
||||
containerInsets: UIEdgeInsets(top: effectiveTabsHeight, left: component.insets.left, bottom: component.insets.bottom, right: component.insets.right),
|
||||
isScrollable: environment.isScrollable
|
||||
)
|
||||
|
||||
let centralPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY))
|
||||
let centralPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))
|
||||
|
||||
if self.animatingTransition {
|
||||
visibleIds = visibleIds.filter({ self.visiblePanels[$0] != nil })
|
||||
|
|
@ -721,7 +485,7 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
)
|
||||
if let panelView = panel.view {
|
||||
if panelView.superview == nil {
|
||||
self.insertSubview(panelView, belowSubview: self.topPanelBackgroundView)
|
||||
self.insertSubview(panelView, belowSubview: self.tabsBackgroundContainer)
|
||||
}
|
||||
|
||||
panelTransition.setFrame(view: panelView, frame: itemFrame, completion: { [weak self] _ in
|
||||
|
|
|
|||
|
|
@ -1122,8 +1122,8 @@ final class StorageUsageScreenComponent: Component {
|
|||
}
|
||||
|
||||
var wasLockedAtPanels = false
|
||||
if let panelContainerView = self.panelContainer.view, let navigationMetrics = self.navigationMetrics {
|
||||
if self.scrollView.bounds.minY > 0.0 && abs(self.scrollView.bounds.minY - (panelContainerView.frame.minY - navigationMetrics.navigationHeight)) <= UIScreenPixel {
|
||||
if let panelContainerView = self.panelContainer.view {
|
||||
if self.scrollView.bounds.minY > 0.0 && abs(self.scrollView.bounds.minY - (panelContainerView.frame.minY - 0.0)) <= UIScreenPixel {
|
||||
wasLockedAtPanels = true
|
||||
}
|
||||
}
|
||||
|
|
@ -2214,7 +2214,7 @@ final class StorageUsageScreenComponent: Component {
|
|||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
dateTimeFormat: environment.dateTimeFormat,
|
||||
insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: bottomInset, right: environment.safeInsets.right),
|
||||
insets: UIEdgeInsets(top: environment.navigationHeight, left: environment.safeInsets.left, bottom: bottomInset, right: environment.safeInsets.right),
|
||||
items: panelItems,
|
||||
currentPanelUpdated: { [weak self] id, transition in
|
||||
guard let self else {
|
||||
|
|
@ -2227,20 +2227,20 @@ final class StorageUsageScreenComponent: Component {
|
|||
environment: {
|
||||
StorageUsagePanelContainerEnvironment(isScrollable: wasLockedAtPanels)
|
||||
},
|
||||
containerSize: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight)
|
||||
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
||||
)
|
||||
if let panelContainerView = self.panelContainer.view {
|
||||
if panelContainerView.superview == nil {
|
||||
self.scrollContainerView.addSubview(panelContainerView)
|
||||
}
|
||||
transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: panelContainerSize))
|
||||
transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight - environment.navigationHeight), size: panelContainerSize))
|
||||
if self.topContentOverlayView.superview == nil {
|
||||
self.scrollContainerView.insertSubview(self.topContentOverlayView, belowSubview: panelContainerView)
|
||||
}
|
||||
self.topContentOverlayView.backgroundColor = environment.theme.list.blocksBackgroundColor
|
||||
transition.setFrame(view: self.topContentOverlayView, frame: CGRect(origin: CGPoint(x: 0.0, y: panelContainerView.frame.minY - availableSize.height), size: availableSize))
|
||||
}
|
||||
contentHeight += panelContainerSize.height
|
||||
contentHeight += panelContainerSize.height - environment.navigationHeight
|
||||
} else {
|
||||
self.panelContainer.view?.removeFromSuperview()
|
||||
self.topContentOverlayView.removeFromSuperview()
|
||||
|
|
@ -2259,7 +2259,7 @@ final class StorageUsageScreenComponent: Component {
|
|||
var scrollViewBounds = self.scrollView.bounds
|
||||
scrollViewBounds.size = availableSize
|
||||
if wasLockedAtPanels, let panelContainerView = self.panelContainer.view {
|
||||
scrollViewBounds.origin.y = panelContainerView.frame.minY - environment.navigationHeight
|
||||
scrollViewBounds.origin.y = panelContainerView.frame.minY
|
||||
}
|
||||
transition.setBounds(view: self.scrollView, bounds: scrollViewBounds)
|
||||
|
||||
|
|
|
|||
|
|
@ -554,85 +554,96 @@ func presentChatReplyOptions(selfController: ChatControllerImpl, sourceView: UIV
|
|||
|
||||
func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubject: ChatInterfaceState.ReplyMessageSubject) {
|
||||
let _ = selfController.presentVoiceMessageDiscardAlert(action: { [weak selfController] in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
let filter: ChatListNodePeersFilter = [.onlyWriteable, .excludeDisabled, .doNotSearchMessages]
|
||||
var attemptSelectionImpl: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
|
||||
let controller = selfController.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(
|
||||
context: selfController.context,
|
||||
updatedPresentationData: selfController.updatedPresentationData,
|
||||
filter: filter,
|
||||
hasFilters: true,
|
||||
title: selfController.presentationData.strings.Conversation_MoveReplyToAnotherChatTitle,
|
||||
attemptSelection: { peer, _, reason in
|
||||
attemptSelectionImpl?(peer, reason)
|
||||
},
|
||||
multipleSelection: false,
|
||||
forwardedMessageIds: [],
|
||||
selectForumThreads: true
|
||||
))
|
||||
let context = selfController.context
|
||||
attemptSelectionImpl = { [weak selfController, weak controller] peer, reason in
|
||||
guard let selfController, let controller = controller else {
|
||||
Task { @MainActor in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
switch reason {
|
||||
case .generic:
|
||||
controller.present(textAlertController(context: context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: presentationData.strings.Forward_ErrorDisabledForChat, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
case .premiumRequired:
|
||||
controller.forEachController { c in
|
||||
if let c = c as? UndoOverlayController {
|
||||
c.dismiss()
|
||||
let filter: ChatListNodePeersFilter = [.onlyWriteable, .excludeDisabled, .doNotSearchMessages]
|
||||
var attemptSelectionImpl: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
|
||||
|
||||
var suggestedPeers: [EnginePeer] = []
|
||||
if let message = await selfController.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Messages.Message(id: replySubject.messageId)
|
||||
).get(), case let .user(author) = message.author {
|
||||
suggestedPeers.append(.user(author))
|
||||
}
|
||||
|
||||
let controller = selfController.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(
|
||||
context: selfController.context,
|
||||
updatedPresentationData: selfController.updatedPresentationData,
|
||||
filter: filter,
|
||||
hasFilters: true,
|
||||
title: selfController.presentationData.strings.Conversation_MoveReplyToAnotherChatTitle,
|
||||
attemptSelection: { peer, _, reason in
|
||||
attemptSelectionImpl?(peer, reason)
|
||||
},
|
||||
multipleSelection: false,
|
||||
forwardedMessageIds: [],
|
||||
selectForumThreads: true,
|
||||
suggestedPeers: suggestedPeers
|
||||
))
|
||||
let context = selfController.context
|
||||
attemptSelectionImpl = { [weak selfController, weak controller] peer, reason in
|
||||
guard let selfController, let controller = controller else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
switch reason {
|
||||
case .generic:
|
||||
controller.present(textAlertController(context: context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: presentationData.strings.Forward_ErrorDisabledForChat, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
case .premiumRequired:
|
||||
controller.forEachController { c in
|
||||
if let c = c as? UndoOverlayController {
|
||||
c.dismiss()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var hasAction = false
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: selfController.context.currentAppConfiguration.with { $0 })
|
||||
if !premiumConfiguration.isPremiumDisabled {
|
||||
hasAction = true
|
||||
}
|
||||
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: hasAction ? presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action : nil, timeout: nil, linkAction: { _ in
|
||||
}), elevatedLayout: false, animateInAsReplacement: true, action: { [weak selfController, weak controller] action in
|
||||
guard let selfController, let controller else {
|
||||
|
||||
var hasAction = false
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: selfController.context.currentAppConfiguration.with { $0 })
|
||||
if !premiumConfiguration.isPremiumDisabled {
|
||||
hasAction = true
|
||||
}
|
||||
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: hasAction ? presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action : nil, timeout: nil, linkAction: { _ in
|
||||
}), elevatedLayout: false, animateInAsReplacement: true, action: { [weak selfController, weak controller] action in
|
||||
guard let selfController, let controller else {
|
||||
return false
|
||||
}
|
||||
if case .undo = action {
|
||||
let premiumController = PremiumIntroScreen(context: selfController.context, source: .settings)
|
||||
controller.push(premiumController)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if case .undo = action {
|
||||
let premiumController = PremiumIntroScreen(context: selfController.context, source: .settings)
|
||||
controller.push(premiumController)
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
}), in: .current)
|
||||
}
|
||||
}
|
||||
controller.peerSelected = { [weak selfController, weak controller] peer, threadId in
|
||||
guard let selfController, let strongController = controller else {
|
||||
return
|
||||
}
|
||||
let peerId = peer.id
|
||||
//let accountPeerId = selfController.context.account.peerId
|
||||
|
||||
var isPinnedMessages = false
|
||||
if case .pinnedMessages = selfController.presentationInterfaceState.subject {
|
||||
isPinnedMessages = true
|
||||
}
|
||||
|
||||
if case .peer(peerId) = selfController.chatLocation, selfController.parentController == nil, !isPinnedMessages {
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) })
|
||||
selfController.updateItemNodesSearchTextHighlightStates()
|
||||
selfController.searchResultsController = nil
|
||||
strongController.dismiss()
|
||||
} else {
|
||||
moveReplyToChat(selfController: selfController, peerId: peerId, threadId: threadId, replySubject: replySubject, completion: { [weak strongController] in
|
||||
strongController?.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
selfController.chatDisplayNode.dismissInput()
|
||||
selfController.effectiveNavigationController?.pushViewController(controller)
|
||||
}
|
||||
controller.peerSelected = { [weak selfController, weak controller] peer, threadId in
|
||||
guard let selfController, let strongController = controller else {
|
||||
return
|
||||
}
|
||||
let peerId = peer.id
|
||||
//let accountPeerId = selfController.context.account.peerId
|
||||
|
||||
var isPinnedMessages = false
|
||||
if case .pinnedMessages = selfController.presentationInterfaceState.subject {
|
||||
isPinnedMessages = true
|
||||
}
|
||||
|
||||
if case .peer(peerId) = selfController.chatLocation, selfController.parentController == nil, !isPinnedMessages {
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) })
|
||||
selfController.updateItemNodesSearchTextHighlightStates()
|
||||
selfController.searchResultsController = nil
|
||||
strongController.dismiss()
|
||||
} else {
|
||||
moveReplyToChat(selfController: selfController, peerId: peerId, threadId: threadId, replySubject: replySubject, completion: { [weak strongController] in
|
||||
strongController?.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
selfController.chatDisplayNode.dismissInput()
|
||||
selfController.effectiveNavigationController?.pushViewController(controller)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
placeholder = placeholderValue
|
||||
let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), chatListFilter: chatListFilter, previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters, displayAutoremoveTimeout: chatSelection.displayAutoremoveTimeout, displayPresence: chatSelection.displayPresence), isPeerEnabled: isPeerEnabled, theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false, autoSetReady: true, isMainTab: false)
|
||||
let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), chatListFilter: chatListFilter, previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], topPeers: [], chatListFilters: chatListFilters, displayAutoremoveTimeout: chatSelection.displayAutoremoveTimeout, displayPresence: chatSelection.displayPresence), isPeerEnabled: isPeerEnabled, theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false, autoSetReady: true, isMainTab: false)
|
||||
chatListNode.passthroughPeerSelection = true
|
||||
chatListNode.disabledPeerSelected = { peer, _, reason in
|
||||
attemptDisabledItemSelection?(peer, reason)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue