mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Folder improvements
This commit is contained in:
parent
05518d735d
commit
3825ddd778
@ -92,6 +92,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/QrCodeUI",
|
||||
"//submodules/TelegramUI/Components/ActionPanelComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -2769,27 +2769,29 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return
|
||||
}
|
||||
|
||||
let previewScreen = ChatFolderLinkPreviewScreen(
|
||||
context: self.context,
|
||||
subject: .linkList(folderId: filterId, initialLinks: links ?? []),
|
||||
contents: ChatFolderLinkContents(
|
||||
localFilterId: filterId, title: title,
|
||||
peers: [],
|
||||
alreadyMemberPeerIds: Set(),
|
||||
memberCounts: [:]
|
||||
),
|
||||
completion: nil
|
||||
)
|
||||
self.push(previewScreen)
|
||||
if links == nil || links?.count == 0 {
|
||||
openCreateChatListFolderLink(context: self.context, folderId: filterId, checkIfExists: false, title: title, peerIds: data.includePeers.peers, pushController: { [weak self] c in
|
||||
self?.push(c)
|
||||
}, presentController: { [weak self] c in
|
||||
self?.present(c, in: .window(.root))
|
||||
}, completed: {
|
||||
}, linkUpdated: { _ in
|
||||
})
|
||||
} else {
|
||||
let previewScreen = ChatFolderLinkPreviewScreen(
|
||||
context: self.context,
|
||||
subject: .linkList(folderId: filterId, initialLinks: links ?? []),
|
||||
contents: ChatFolderLinkContents(
|
||||
localFilterId: filterId, title: title,
|
||||
peers: [],
|
||||
alreadyMemberPeerIds: Set(),
|
||||
memberCounts: [:]
|
||||
),
|
||||
completion: nil
|
||||
)
|
||||
self.push(previewScreen)
|
||||
}
|
||||
})
|
||||
|
||||
/*openCreateChatListFolderLink(context: self.context, folderId: filterId, checkIfExists: true, title: title, peerIds: data.includePeers.peers, pushController: { [weak self] c in
|
||||
self?.push(c)
|
||||
}, presentController: { [weak self] c in
|
||||
self?.present(c, in: .window(.root))
|
||||
}, completed: {
|
||||
}, linkUpdated: { _ in
|
||||
})*/
|
||||
}
|
||||
|
||||
public func navigateToFolder(folderId: Int32, completion: @escaping () -> Void) {
|
||||
|
@ -14,6 +14,10 @@ import ContextUI
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import TelegramUIPreferences
|
||||
import ActionPanelComponent
|
||||
import ComponentDisplayAdapters
|
||||
import ComponentFlow
|
||||
import ChatFolderLinkPreviewScreen
|
||||
|
||||
public enum ChatListContainerNodeFilter: Equatable {
|
||||
case all
|
||||
@ -316,6 +320,14 @@ private final class ChatListShimmerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
private final class TopPanelItem {
|
||||
let view = ComponentView<Empty>()
|
||||
var size: CGSize?
|
||||
|
||||
init() {
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let animationCache: AnimationCache
|
||||
private let animationRenderer: MultiAnimationRenderer
|
||||
@ -332,6 +344,13 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
private var shimmerNodeOffset: CGFloat = 0.0
|
||||
let listNode: ChatListNode
|
||||
|
||||
private var topPanel: TopPanelItem?
|
||||
|
||||
private var pollFilterUpdatesDisposable: Disposable?
|
||||
private var chatFilterUpdatesDisposable: Disposable?
|
||||
|
||||
private var chatFolderUpdates: ChatFolderUpdates?
|
||||
|
||||
private(set) var validLayout: (size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat)?
|
||||
|
||||
init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) {
|
||||
@ -456,7 +475,40 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
if let (size, insets, _, _, _, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition)
|
||||
}
|
||||
strongSelf.layoutAdditionalPanels(transition: transition)
|
||||
}
|
||||
|
||||
if let filter, case let .filter(id, _, _, data) = filter, data.isShared {
|
||||
self.pollFilterUpdatesDisposable = self.context.engine.peers.pollChatFolderUpdates(folderId: id).start()
|
||||
self.chatFilterUpdatesDisposable = (self.context.engine.peers.subscribedChatFolderUpdates(folderId: id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
var update = false
|
||||
if let result, result.availableChatsToJoin != 0 {
|
||||
if self.chatFolderUpdates?.availableChatsToJoin != result.availableChatsToJoin {
|
||||
update = true
|
||||
}
|
||||
self.chatFolderUpdates = result
|
||||
} else {
|
||||
if self.chatFolderUpdates != nil {
|
||||
self.chatFolderUpdates = nil
|
||||
update = true
|
||||
}
|
||||
}
|
||||
if update {
|
||||
if let (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction) = self.validLayout {
|
||||
self.updateLayout(size: size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.pollFilterUpdatesDisposable?.dispose()
|
||||
self.chatFilterUpdatesDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
@ -464,6 +516,32 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size))
|
||||
}
|
||||
|
||||
private func layoutAdditionalPanels(transition: ContainedViewLayoutTransition) {
|
||||
guard let (size, insets, visualNavigationHeight, _, _, _) = self.validLayout, let offset = self.floatingHeaderOffset else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = size
|
||||
let _ = insets
|
||||
|
||||
if let topPanel = self.topPanel, let topPanelSize = topPanel.size {
|
||||
let minY: CGFloat = visualNavigationHeight - 44.0 + topPanelSize.height
|
||||
|
||||
if let topPanelView = topPanel.view.view {
|
||||
var animateIn = false
|
||||
var topPanelTransition = transition
|
||||
if topPanelView.bounds.isEmpty {
|
||||
topPanelTransition = .immediate
|
||||
animateIn = true
|
||||
}
|
||||
topPanelTransition.updateFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: max(minY, offset - topPanelSize.height)), size: topPanelSize))
|
||||
if animateIn {
|
||||
transition.animatePositionAdditive(layer: topPanelView.layer, offset: CGPoint(x: 0.0, y: -topPanelView.bounds.height))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
@ -479,17 +557,85 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction)
|
||||
|
||||
var listInsets = insets
|
||||
var additionalTopInset: CGFloat = 0.0
|
||||
|
||||
if let chatFolderUpdates = self.chatFolderUpdates {
|
||||
let topPanel: TopPanelItem
|
||||
var topPanelTransition = Transition(transition)
|
||||
if let current = self.topPanel {
|
||||
topPanel = current
|
||||
} else {
|
||||
topPanelTransition = .immediate
|
||||
topPanel = TopPanelItem()
|
||||
self.topPanel = topPanel
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let title: String
|
||||
if chatFolderUpdates.availableChatsToJoin == 1 {
|
||||
title = "1 New Chat Available"
|
||||
} else {
|
||||
title = "\(chatFolderUpdates.availableChatsToJoin) New Chats Available"
|
||||
}
|
||||
|
||||
let topPanelHeight: CGFloat = 44.0
|
||||
|
||||
let _ = topPanel.view.update(
|
||||
transition: topPanelTransition,
|
||||
component: AnyComponent(ActionPanelComponent(
|
||||
theme: self.presentationData.theme,
|
||||
title: title,
|
||||
action: { [weak self] in
|
||||
guard let self, let chatFolderUpdates = self.chatFolderUpdates else {
|
||||
return
|
||||
}
|
||||
|
||||
self.listNode.push?(ChatFolderLinkPreviewScreen(context: self.context, subject: .updates(chatFolderUpdates), contents: chatFolderUpdates.chatFolderLinkContents))
|
||||
},
|
||||
dismissAction: { [weak self] in
|
||||
guard let self, let chatFolderUpdates = self.chatFolderUpdates else {
|
||||
return
|
||||
}
|
||||
let _ = self.context.engine.peers.hideChatFolderUpdates(folderId: chatFolderUpdates.folderId).start()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width, height: topPanelHeight)
|
||||
)
|
||||
if let topPanelView = topPanel.view.view {
|
||||
if topPanelView.superview == nil {
|
||||
self.view.addSubview(topPanelView)
|
||||
}
|
||||
}
|
||||
|
||||
topPanel.size = CGSize(width: size.width, height: topPanelHeight)
|
||||
listInsets.top += topPanelHeight
|
||||
additionalTopInset += topPanelHeight
|
||||
} else {
|
||||
if let topPanel = self.topPanel {
|
||||
self.topPanel = nil
|
||||
if let topPanelView = topPanel.view.view {
|
||||
transition.updatePosition(layer: topPanelView.layer, position: CGPoint(x: topPanelView.layer.position.x, y: topPanelView.layer.position.y - topPanelView.layer.bounds.height), completion: { [weak topPanelView] _ in
|
||||
topPanelView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve)
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: listInsets, duration: duration, curve: curve)
|
||||
|
||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight, originalTopInset: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction)
|
||||
self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight + additionalTopInset, originalTopInset: originalNavigationHeight + additionalTopInset, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction)
|
||||
|
||||
if let emptyNode = self.emptyNode {
|
||||
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
|
||||
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: listInsets.top), size: CGSize(width: size.width, height: size.height - listInsets.top - listInsets.bottom))
|
||||
transition.updateFrame(node: emptyNode, frame: emptyNodeFrame)
|
||||
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: transition)
|
||||
}
|
||||
|
||||
self.layoutAdditionalPanels(transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
let openLink: (ExportedChatFolderLink) -> Void
|
||||
let removeLink: (ExportedChatFolderLink) -> Void
|
||||
let linkContextAction: (ExportedChatFolderLink?, ASDisplayNode, ContextGesture?) -> Void
|
||||
let peerContextAction: (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
@ -59,7 +60,8 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
createLink: @escaping () -> Void,
|
||||
openLink: @escaping (ExportedChatFolderLink) -> Void,
|
||||
removeLink: @escaping (ExportedChatFolderLink) -> Void,
|
||||
linkContextAction: @escaping (ExportedChatFolderLink?, ASDisplayNode, ContextGesture?) -> Void
|
||||
linkContextAction: @escaping (ExportedChatFolderLink?, ASDisplayNode, ContextGesture?) -> Void,
|
||||
peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
@ -77,6 +79,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
self.openLink = openLink
|
||||
self.removeLink = removeLink
|
||||
self.linkContextAction = linkContextAction
|
||||
self.peerContextAction = peerContextAction
|
||||
}
|
||||
}
|
||||
|
||||
@ -514,6 +517,12 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) })
|
||||
}, removePeer: { id in
|
||||
arguments.deleteIncludePeer(id)
|
||||
}, contextAction: { sourceNode, gesture in
|
||||
guard let peer = peer.peer else {
|
||||
gesture?.cancel()
|
||||
return
|
||||
}
|
||||
arguments.peerContextAction(peer, sourceNode, gesture, nil)
|
||||
})
|
||||
case let .excludePeer(_, peer, isRevealed):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
|
||||
@ -522,6 +531,12 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) })
|
||||
}, removePeer: { id in
|
||||
arguments.deleteExcludePeer(id)
|
||||
}, contextAction: { sourceNode, gesture in
|
||||
guard let peer = peer.peer else {
|
||||
gesture?.cancel()
|
||||
return
|
||||
}
|
||||
arguments.peerContextAction(peer, sourceNode, gesture, nil)
|
||||
})
|
||||
case let .includeExpand(text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
@ -1472,6 +1487,32 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
},
|
||||
peerContextAction: { peer, node, gesture, location in
|
||||
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Delete, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
updateState { state in
|
||||
var state = state
|
||||
if let index = state.additionallyExcludePeers.firstIndex(of: peer.id) {
|
||||
state.additionallyExcludePeers.remove(at: index)
|
||||
}
|
||||
if let index = state.additionallyIncludePeers.firstIndex(of: peer.id) {
|
||||
state.additionallyIncludePeers.remove(at: index)
|
||||
}
|
||||
return state
|
||||
}
|
||||
})))
|
||||
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
}
|
||||
)
|
||||
|
||||
@ -1798,3 +1839,31 @@ private final class InviteLinkContextReferenceContentSource: ContextReferenceCon
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
weak var sourceNode: ASDisplayNode?
|
||||
|
||||
let navigationController: NavigationController? = nil
|
||||
|
||||
let passthroughTouches: Bool = true
|
||||
|
||||
init(controller: ViewController, sourceNode: ASDisplayNode?) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||
let sourceNode = self.sourceNode
|
||||
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
||||
if let sourceNode = sourceNode {
|
||||
return (sourceNode.view, sourceNode.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func animatedIn() {
|
||||
}
|
||||
}
|
||||
|
@ -1669,6 +1669,30 @@ public final class ChatListNode: ListView {
|
||||
|
||||
let currentPeerId: EnginePeer.Id = context.account.peerId
|
||||
|
||||
|
||||
/*let emptyInitialView = ChatListNodeView(
|
||||
originalList: EngineChatList(
|
||||
items: [],
|
||||
groupItems: [],
|
||||
additionalItems: [],
|
||||
hasEarlier: false,
|
||||
hasLater: false,
|
||||
isLoading: false
|
||||
),
|
||||
filteredEntries: [ChatListNodeEntry.HeaderEntry],
|
||||
isLoading: false,
|
||||
filter: nil
|
||||
)
|
||||
let _ = previousView.swap(emptyInitialView)
|
||||
|
||||
let _ = (preparedChatListNodeViewTransition(from: nil, to: emptyInitialView, reason: .initial, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: nil, searchMode: false)
|
||||
|> map { mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: nil, mode: mode, isPeerEnabled: nil, transition: $0) }).start(next: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self.enqueueTransition(value).start()
|
||||
})*/
|
||||
|
||||
let chatListNodeViewTransition = combineLatest(
|
||||
queue: viewProcessingQueue,
|
||||
hideArchivedFolderByDefault,
|
||||
@ -1949,7 +1973,7 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
if isEmpty {
|
||||
entries = []
|
||||
entries = [.HeaderEntry]
|
||||
}
|
||||
|
||||
let processedView = ChatListNodeView(originalList: update.list, filteredEntries: entries, isLoading: isLoading, filter: filter)
|
||||
@ -1964,6 +1988,8 @@ public final class ChatListNode: ListView {
|
||||
if previous.filteredEntries.count == 1 {
|
||||
if case .HoleEntry = previous.filteredEntries[0] {
|
||||
previousWasEmptyOrSingleHole = true
|
||||
} else if case .HeaderEntry = previous.filteredEntries[0] {
|
||||
previousWasEmptyOrSingleHole = true
|
||||
}
|
||||
} else if previous.filteredEntries.isEmpty && previous.isLoading {
|
||||
previousWasEmptyOrSingleHole = true
|
||||
@ -2655,7 +2681,9 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
|
||||
private func pollFilterUpdates() {
|
||||
guard let chatListFilter, case let .filter(id, _, _, data) = chatListFilter, data.isShared else {
|
||||
self.chatFolderUpdates.set(.single(nil))
|
||||
|
||||
/*guard let chatListFilter, case let .filter(id, _, _, data) = chatListFilter, data.isShared else {
|
||||
self.chatFolderUpdates.set(.single(nil))
|
||||
return
|
||||
}
|
||||
@ -2666,7 +2694,7 @@ public final class ChatListNode: ListView {
|
||||
return
|
||||
}
|
||||
self.chatFolderUpdates.set(.single(result))
|
||||
})
|
||||
})*/
|
||||
}
|
||||
|
||||
private func resetFilter() {
|
||||
@ -2960,7 +2988,8 @@ public final class ChatListNode: ListView {
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
||||
}
|
||||
|
||||
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatListOpaqueTransactionState(chatListView: transition.chatListView), completion: completion)
|
||||
let updatedOpaqueState: Any? = ChatListOpaqueTransactionState(chatListView: transition.chatListView)
|
||||
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: updatedOpaqueState, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2968,6 +2997,8 @@ public final class ChatListNode: ListView {
|
||||
switch self.visibleContentOffset() {
|
||||
case let .known(value) where abs(value) < navigationBarSearchContentHeight - 1.0:
|
||||
return false
|
||||
case .none:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
@ -3070,6 +3101,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
if !self.dequeuedInitialTransitionOnLayout {
|
||||
self.dequeuedInitialTransitionOnLayout = true
|
||||
|
||||
self.dequeueTransition()
|
||||
}
|
||||
}
|
||||
|
@ -311,24 +311,49 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
if let currentBadge = self.currentBadge, currentBadge.badge == badge {
|
||||
badgeImage = currentBadge.image
|
||||
} else {
|
||||
let badgeTextColor: UIColor = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||
let badgeString = NSAttributedString(string: badge.value, font: Font.semibold(11.0), textColor: badgeTextColor)
|
||||
let badgeTextBounds = badgeString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
|
||||
let badgeSideInset: CGFloat = 3.0
|
||||
let badgeVerticalInset: CGFloat = 1.0
|
||||
let badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + ceil(badgeTextBounds.width), height: badgeVerticalInset * 2.0 + ceil(badgeTextBounds.height))
|
||||
badgeImage = generateImage(badgeBackgroundSize, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 5.0).cgPath)
|
||||
context.fillPath()
|
||||
switch badge.style {
|
||||
case .badge:
|
||||
let badgeTextColor: UIColor = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||
let badgeString = NSAttributedString(string: badge.value, font: Font.regular(13.0), textColor: badgeTextColor)
|
||||
let badgeTextBounds = badgeString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
let badgeSideInset: CGFloat = 5.0
|
||||
let badgeVerticalInset: CGFloat = 1.0
|
||||
var badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + ceil(badgeTextBounds.width), height: badgeVerticalInset * 2.0 + ceil(badgeTextBounds.height))
|
||||
badgeBackgroundSize.width = max(badgeBackgroundSize.width, badgeBackgroundSize.height)
|
||||
badgeImage = generateImage(badgeBackgroundSize, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath)
|
||||
context.fillPath()
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
badgeString.draw(at: CGPoint(x: badgeTextBounds.minX + floor((badgeBackgroundSize.width - badgeTextBounds.width) * 0.5), y: badgeTextBounds.minY + badgeVerticalInset))
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
case .label:
|
||||
let badgeTextColor: UIColor = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||
let badgeString = NSAttributedString(string: badge.value, font: Font.semibold(11.0), textColor: badgeTextColor)
|
||||
let badgeTextBounds = badgeString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
|
||||
|
||||
badgeString.draw(at: CGPoint(x: badgeTextBounds.minX + badgeSideInset + UIScreenPixel, y: badgeTextBounds.minY + badgeVerticalInset + UIScreenPixel))
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
let badgeSideInset: CGFloat = 3.0
|
||||
let badgeVerticalInset: CGFloat = 1.0
|
||||
let badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + ceil(badgeTextBounds.width), height: badgeVerticalInset * 2.0 + ceil(badgeTextBounds.height))
|
||||
badgeImage = generateImage(badgeBackgroundSize, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 5.0).cgPath)
|
||||
context.fillPath()
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
badgeString.draw(at: CGPoint(x: badgeTextBounds.minX + badgeSideInset + UIScreenPixel, y: badgeTextBounds.minY + badgeVerticalInset + UIScreenPixel))
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let badgeIconNode: ASImageNode
|
||||
|
@ -504,7 +504,7 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|
||||
}
|
||||
|
||||
public final class ChatFolderUpdates: Equatable {
|
||||
fileprivate let folderId: Int32
|
||||
public let folderId: Int32
|
||||
fileprivate let title: String
|
||||
fileprivate let missingPeers: [EnginePeer]
|
||||
fileprivate let memberCounts: [EnginePeer.Id: Int]
|
||||
|
23
submodules/TelegramUI/Components/ActionPanelComponent/BUILD
Normal file
23
submodules/TelegramUI/Components/ActionPanelComponent/BUILD
Normal file
@ -0,0 +1,23 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ActionPanelComponent",
|
||||
module_name = "ActionPanelComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/AnimatedCountLabelNode",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/AppBundle",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,172 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import AppBundle
|
||||
|
||||
public final class ActionPanelComponent: Component {
|
||||
public let theme: PresentationTheme
|
||||
public let title: String
|
||||
public let action: () -> Void
|
||||
public let dismissAction: () -> Void
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
action: @escaping () -> Void,
|
||||
dismissAction: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.action = action
|
||||
self.dismissAction = dismissAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: ActionPanelComponent, rhs: ActionPanelComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: HighlightTrackingButton {
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private let separatorLayer: SimpleLayer
|
||||
|
||||
private let contentView: UIView
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
private let dismissButton: HighlightTrackingButton
|
||||
private let dismissIconView: UIImageView
|
||||
|
||||
private var component: ActionPanelComponent?
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
self.backgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
|
||||
self.backgroundView.isUserInteractionEnabled = false
|
||||
|
||||
self.separatorLayer = SimpleLayer()
|
||||
self.contentView = UIView()
|
||||
self.contentView.isUserInteractionEnabled = false
|
||||
|
||||
self.dismissButton = HighlightTrackingButton()
|
||||
self.dismissIconView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.layer.addSublayer(self.separatorLayer)
|
||||
self.addSubview(self.contentView)
|
||||
|
||||
self.dismissButton.addSubview(self.dismissIconView)
|
||||
self.addSubview(self.dismissButton)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let self {
|
||||
if highlighted {
|
||||
self.contentView.layer.removeAnimation(forKey: "opacity")
|
||||
self.contentView.alpha = 0.65
|
||||
} else {
|
||||
self.contentView.alpha = 1.0
|
||||
self.contentView.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
self.dismissButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let self {
|
||||
if highlighted {
|
||||
self.dismissButton.layer.removeAnimation(forKey: "opacity")
|
||||
self.dismissButton.alpha = 0.65
|
||||
} else {
|
||||
self.dismissButton.alpha = 1.0
|
||||
self.dismissButton.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dismissButton.addTarget(self, action: #selector(self.dismissPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action()
|
||||
}
|
||||
|
||||
@objc private func dismissPressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.dismissAction()
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func update(component: ActionPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let themeUpdated = self.component?.theme !== component.theme
|
||||
|
||||
self.component = component
|
||||
|
||||
if themeUpdated {
|
||||
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||
self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor
|
||||
|
||||
self.dismissIconView.image = UIImage(bundleImageName: "Chat/Input/Accessory Panels/EncircledCloseButton")?.withRenderingMode(.alwaysTemplate)
|
||||
self.dismissIconView.tintColor = component.theme.rootController.navigationBar.accentTextColor
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
self.backgroundView.update(size: availableSize, transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
|
||||
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let rightInset: CGFloat = 44.0
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(text: component.title, font: Font.regular(17.0), color: component.theme.rootController.navigationBar.accentTextColor)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - rightInset, height: availableSize.height)
|
||||
)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.layer.anchorPoint = CGPoint()
|
||||
self.contentView.addSubview(titleView)
|
||||
}
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((availableSize.height - titleSize.height) * 0.5)), size: titleSize)
|
||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
}
|
||||
|
||||
let dismissButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset, y: 0.0), size: CGSize(width: rightInset, height: availableSize.height))
|
||||
transition.setFrame(view: self.dismissButton, frame: dismissButtonFrame)
|
||||
if let iconImage = self.dismissIconView.image {
|
||||
transition.setFrame(view: self.dismissIconView, frame: CGRect(origin: CGPoint(x: floor((dismissButtonFrame.width - iconImage.size.width) * 0.5), y: floor((dismissButtonFrame.height - iconImage.size.height) * 0.5)), size: iconImage.size))
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user