mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-15 18:59:54 +00:00
Support for chat list editing actions
This commit is contained in:
parent
394b204f29
commit
e9da154ac3
@ -408,6 +408,7 @@
|
||||
D0C27B3D1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B3C1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift */; };
|
||||
D0C44B641FC64D0500227BE0 /* SwipeToDismissGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C44B631FC64D0500227BE0 /* SwipeToDismissGestureRecognizer.swift */; };
|
||||
D0C45E9F213FFAFD00988156 /* Lottie.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C45E9E213FFAFD00988156 /* Lottie.framework */; };
|
||||
D0C683FC21AD797F00A6CAD5 /* ChatListSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C683FB21AD797F00A6CAD5 /* ChatListSelection.swift */; };
|
||||
D0CAD8FB20AE1D1B00ACD96E /* ChannelMemberCategoryListContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CAD8FA20AE1D1B00ACD96E /* ChannelMemberCategoryListContext.swift */; };
|
||||
D0CAD8FD20AE467D00ACD96E /* PeerChannelMemberCategoriesContextsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CAD8FC20AE467D00ACD96E /* PeerChannelMemberCategoriesContextsManager.swift */; };
|
||||
D0CAD90120AEECAC00ACD96E /* ChatEditInterfaceMessageState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CAD90020AEECAC00ACD96E /* ChatEditInterfaceMessageState.swift */; };
|
||||
@ -1808,6 +1809,7 @@
|
||||
D0C50E3D1E93D09200F62E39 /* NotificationItemContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationItemContainerNode.swift; sourceTree = "<group>"; };
|
||||
D0C50E3F1E93D3B000F62E39 /* ChatMessageNotificationItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageNotificationItem.swift; sourceTree = "<group>"; };
|
||||
D0C50E431E93FCD200F62E39 /* notification.caf */ = {isa = PBXFileReference; lastKnownFileType = file; name = notification.caf; path = TelegramUI/Sounds/notification.caf; sourceTree = "<group>"; };
|
||||
D0C683FB21AD797F00A6CAD5 /* ChatListSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListSelection.swift; sourceTree = "<group>"; };
|
||||
D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatButtonKeyboardInputNode.swift; sourceTree = "<group>"; };
|
||||
D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatBotInfoItem.swift; sourceTree = "<group>"; };
|
||||
D0C9323B1E0B4AE90074F044 /* DataAndStorageSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataAndStorageSettingsController.swift; sourceTree = "<group>"; };
|
||||
@ -4219,6 +4221,7 @@
|
||||
D0575AEA1E9FD579006F2541 /* ChatListTitleLockView.swift */,
|
||||
D07E413A208A432100FCA8F0 /* ChatListTitleProxyNode.swift */,
|
||||
D06E4C302134910400088087 /* ChatListEmptyNode.swift */,
|
||||
D0C683FB21AD797F00A6CAD5 /* ChatListSelection.swift */,
|
||||
D0F69E051D6B8A8B0046BCD6 /* Search */,
|
||||
);
|
||||
name = "Chat List";
|
||||
@ -5283,6 +5286,7 @@
|
||||
D0EC6D681EB9F58800EBF1C3 /* AuthorizationSequenceController.swift in Sources */,
|
||||
D0EC6D691EB9F58800EBF1C3 /* AuthorizationSequenceSplashController.swift in Sources */,
|
||||
D0EC6D6A1EB9F58800EBF1C3 /* AuthorizationSequenceSplashControllerNode.swift in Sources */,
|
||||
D0C683FC21AD797F00A6CAD5 /* ChatListSelection.swift in Sources */,
|
||||
D0EC6D6B1EB9F58800EBF1C3 /* AuthorizationSequenceCountrySelectionController.swift in Sources */,
|
||||
D0EC6D6C1EB9F58800EBF1C3 /* AuthorizationSequenceCountrySelectionControllerNode.swift in Sources */,
|
||||
D0BFAE5D20AB426300793CF2 /* PeerTitle.swift in Sources */,
|
||||
|
||||
@ -274,7 +274,7 @@ public func archivedStickerPacksController(account: Account, archived: [Archived
|
||||
if !add {
|
||||
return
|
||||
}
|
||||
let _ = (loadedStickerPack(postbox: account.postbox, network: account.network, reference: .id(id: info.id.id, accessHash: info.accessHash))
|
||||
let _ = (loadedStickerPack(postbox: account.postbox, network: account.network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
switch result {
|
||||
case let .result(info, items, installed):
|
||||
|
||||
@ -590,7 +590,17 @@ func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messag
|
||||
case .creator, .admin:
|
||||
optionsMap[id]!.insert(.deleteGlobally)
|
||||
case .member:
|
||||
break
|
||||
var hasMediaToReport = false
|
||||
for media in message.media {
|
||||
if let _ = media as? TelegramMediaImage {
|
||||
hasMediaToReport = true
|
||||
} else if let file = media as? TelegramMediaFile, file.isVideo {
|
||||
hasMediaToReport = true
|
||||
}
|
||||
}
|
||||
if hasMediaToReport {
|
||||
optionsMap[id]!.insert(.report)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let _ = peer as? TelegramUser {
|
||||
|
||||
@ -37,8 +37,11 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
private var didSuggestLocalization = false
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private let presentationDataValue = Promise<PresentationData>()
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private let stateDisposable = MetaDisposable()
|
||||
|
||||
public init(account: Account, groupId: PeerGroupId?, controlsHistoryPreload: Bool) {
|
||||
self.account = account
|
||||
self.controlsHistoryPreload = controlsHistoryPreload
|
||||
@ -46,6 +49,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
self.groupId = groupId
|
||||
|
||||
self.presentationData = (account.telegramApplicationContext.currentPresentationData.with { $0 })
|
||||
self.presentationDataValue.set(.single(self.presentationData))
|
||||
|
||||
self.titleView = NetworkStatusTitleView(theme: self.presentationData.theme)
|
||||
|
||||
@ -207,18 +211,19 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
strongSelf.presentationDataValue.set(.single(presentationData))
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -233,6 +238,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
self.passcodeLockTooltipDisposable.dispose()
|
||||
self.suggestLocalizationDisposable.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.stateDisposable.dispose()
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
@ -469,6 +475,41 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
}
|
||||
}
|
||||
|
||||
let account = self.account
|
||||
let peerIdsAndOptions: Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> = self.chatListDisplayNode.chatListNode.state
|
||||
|> map { state -> Set<PeerId>? in
|
||||
if !state.editing {
|
||||
return nil
|
||||
}
|
||||
return state.selectedPeerIds
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { selectedPeerIds -> Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> in
|
||||
if let selectedPeerIds = selectedPeerIds {
|
||||
return chatListSelectionOptions(postbox: account.postbox, peerIds: selectedPeerIds)
|
||||
|> map { options -> (ChatListSelectionOptions, Set<PeerId>)? in
|
||||
return (options, selectedPeerIds)
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
||||
self.stateDisposable.set(combineLatest(queue: .mainQueue(), self.presentationDataValue.get(), peerIdsAndOptions).start(next: { [weak self] presentationData, peerIdsAndOptions in
|
||||
var toolbar: Toolbar?
|
||||
if let (options, _) = peerIdsAndOptions {
|
||||
let leftAction: ToolbarAction
|
||||
switch options.read {
|
||||
case let .all(enabled):
|
||||
leftAction = ToolbarAction(title: presentationData.strings.ChatList_ReadAll, isEnabled: enabled)
|
||||
case let .selective(enabled):
|
||||
leftAction = ToolbarAction(title: presentationData.strings.ChatList_Read, isEnabled: enabled)
|
||||
}
|
||||
toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete))
|
||||
}
|
||||
self?.setToolbar(toolbar, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}))
|
||||
|
||||
/*self.badgeIconDisposable = (self.chatListDisplayNode.chatListNode.scrollToTopOption
|
||||
|> distinctUntilChanged
|
||||
|> deliverOnMainQueue).start(next: { [weak self] option in
|
||||
@ -616,7 +657,9 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
self.navigationItem.rightBarButtonItem = editItem
|
||||
}
|
||||
self.chatListDisplayNode.chatListNode.updateState { state in
|
||||
return state.withUpdatedEditing(true)
|
||||
var state = state
|
||||
state.editing = true
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
@ -628,7 +671,11 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
self.navigationItem.rightBarButtonItem = editItem
|
||||
}
|
||||
self.chatListDisplayNode.chatListNode.updateState { state in
|
||||
return state.withUpdatedEditing(false).withUpdatedPeerIdWithRevealedOptions(nil)
|
||||
var state = state
|
||||
state.editing = false
|
||||
state.peerIdWithRevealedOptions = nil
|
||||
state.selectedPeerIds.removeAll()
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
@ -817,4 +864,75 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
KeyShortcut(input: UIKeyInputEscape, modifiers: [], action: toggleSearch)
|
||||
]
|
||||
}
|
||||
|
||||
override public func toolbarActionSelected(left: Bool) {
|
||||
let peerIds = self.chatListDisplayNode.chatListNode.currentState.selectedPeerIds
|
||||
if left {
|
||||
let signal: Signal<Void, NoError>
|
||||
let account = self.account
|
||||
if !peerIds.isEmpty {
|
||||
signal = self.account.postbox.transaction { transaction -> Void in
|
||||
for peerId in peerIds {
|
||||
togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: account.viewTracker, peerId: peerId, setToValue: false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
signal = self.account.postbox.transaction { transaction -> Void in
|
||||
markAllChatsAsReadInteractively(transaction: transaction, viewTracker: account.viewTracker)
|
||||
}
|
||||
}
|
||||
let _ = signal.start(completed: { [weak self] in
|
||||
self?.donePressed()
|
||||
})
|
||||
} else if !peerIds.isEmpty {
|
||||
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let account = strongSelf.account
|
||||
let presentationData = strongSelf.presentationData
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
||||
self?.present(controller, in: .window(.root))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.8, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.start()
|
||||
|
||||
let signal: Signal<Void, NoError> = strongSelf.account.postbox.transaction { transaction -> Void in
|
||||
for peerId in peerIds {
|
||||
removePeerChat(transaction: transaction, mediaBox: account.postbox.mediaBox, peerId: peerId, reportChatSpam: false)
|
||||
}
|
||||
}
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
let _ = signal.start(completed: {
|
||||
self?.donePressed()
|
||||
})
|
||||
}))
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
])
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ class ChatListItem: ListViewItem {
|
||||
let content: ChatListItemContent
|
||||
let editing: Bool
|
||||
let hasActiveRevealControls: Bool
|
||||
let selected: Bool
|
||||
let enableContextActions: Bool
|
||||
let interaction: ChatListNodeInteraction
|
||||
|
||||
@ -35,7 +36,7 @@ class ChatListItem: ListViewItem {
|
||||
|
||||
let header: ListViewItemHeader?
|
||||
|
||||
init(presentationData: ChatListPresentationData, account: Account, peerGroupId: PeerGroupId?, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, header: ListViewItemHeader?, enableContextActions: Bool, interaction: ChatListNodeInteraction) {
|
||||
init(presentationData: ChatListPresentationData, account: Account, peerGroupId: PeerGroupId?, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, interaction: ChatListNodeInteraction) {
|
||||
self.presentationData = presentationData
|
||||
self.peerGroupId = peerGroupId
|
||||
self.account = account
|
||||
@ -43,6 +44,7 @@ class ChatListItem: ListViewItem {
|
||||
self.content = content
|
||||
self.editing = editing
|
||||
self.hasActiveRevealControls = hasActiveRevealControls
|
||||
self.selected = selected
|
||||
self.header = header
|
||||
self.enableContextActions = enableContextActions
|
||||
self.interaction = interaction
|
||||
@ -51,7 +53,6 @@ class ChatListItem: ListViewItem {
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||
async {
|
||||
let node = ChatListItemNode()
|
||||
node.setupItem(item: self)
|
||||
let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
node.insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader)
|
||||
|
||||
@ -63,6 +64,7 @@ class ChatListItem: ListViewItem {
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
node.setupItem(item: self)
|
||||
apply(false)
|
||||
node.updateIsHighlighted(transition: .immediate)
|
||||
})
|
||||
@ -231,7 +233,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var verificationIconNode: ASImageNode?
|
||||
let mutedIconNode: ASImageNode
|
||||
|
||||
var editableControlNode: ItemListEditableControlNode?
|
||||
var selectableControlNode: ItemListSelectableControlNode?
|
||||
var reorderControlNode: ItemListEditableReorderControlNode?
|
||||
|
||||
var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams)?
|
||||
@ -239,7 +241,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
private var isHighlighted: Bool = false
|
||||
|
||||
override var canBeSelected: Bool {
|
||||
if self.editableControlNode != nil {
|
||||
if self.selectableControlNode != nil {
|
||||
return false
|
||||
} else {
|
||||
return super.canBeSelected
|
||||
@ -393,6 +395,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func tapped() {
|
||||
guard let item = self.item, item.editing else {
|
||||
return
|
||||
}
|
||||
item.interaction.togglePeerSelected(item.index.messageIndex.id.peerId)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ChatListItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ nextIsPinned: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let dateLayout = TextNode.asyncLayout(self.dateNode)
|
||||
let textLayout = TextNode.asyncLayout(self.textNode)
|
||||
@ -400,7 +409,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let authorLayout = TextNode.asyncLayout(self.authorNode)
|
||||
let inputActivitiesLayout = self.inputActivitiesNode.asyncLayout()
|
||||
let badgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
|
||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
let selectableControlLayout = ItemListSelectableControlNode.asyncLayout(self.selectableControlNode)
|
||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||
|
||||
let currentItem = self.layoutParams?.0
|
||||
@ -488,17 +497,17 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var currentVerificationIconImage: UIImage?
|
||||
var currentSecretIconImage: UIImage?
|
||||
|
||||
var editableControlSizeAndApply: (CGSize, () -> ItemListEditableControlNode)?
|
||||
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||
var reorderControlSizeAndApply: (CGSize, () -> ItemListEditableReorderControlNode)?
|
||||
|
||||
let editingOffset: CGFloat
|
||||
var reorderInset: CGFloat = 0.0
|
||||
if item.editing {
|
||||
let sizeAndApply = editableControlLayout(itemHeight, item.presentationData.theme, isPeerGroup)
|
||||
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, item.selected, true)
|
||||
if !isAd {
|
||||
editableControlSizeAndApply = sizeAndApply
|
||||
selectableControlSizeAndApply = sizeAndApply
|
||||
}
|
||||
editingOffset = sizeAndApply.0.width
|
||||
editingOffset = sizeAndApply.0
|
||||
|
||||
if item.index.pinningIndex != nil && !isAd {
|
||||
let sizeAndApply = reorderControlLayout(itemHeight, item.presentationData.theme)
|
||||
@ -758,32 +767,30 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
var crossfadeContent = false
|
||||
if let editableControlSizeAndApply = editableControlSizeAndApply {
|
||||
if strongSelf.editableControlNode == nil {
|
||||
if let selectableControlSizeAndApply = selectableControlSizeAndApply {
|
||||
let selectableControlSize = CGSize(width: selectableControlSizeAndApply.0, height: layout.contentSize.height)
|
||||
let selectableControlFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset, y: 0.0), size: selectableControlSize)
|
||||
if strongSelf.selectableControlNode == nil {
|
||||
crossfadeContent = true
|
||||
let editableControlNode = editableControlSizeAndApply.1()
|
||||
editableControlNode.tapped = {
|
||||
if let strongSelf = self {
|
||||
strongSelf.setRevealOptionsOpened(true, animated: true)
|
||||
strongSelf.revealOptionsInteractivelyOpened()
|
||||
}
|
||||
}
|
||||
strongSelf.editableControlNode = editableControlNode
|
||||
strongSelf.addSubnode(editableControlNode)
|
||||
let editableControlFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset, y: 0.0), size: editableControlSizeAndApply.0)
|
||||
editableControlNode.frame = editableControlFrame
|
||||
transition.animatePosition(node: editableControlNode, from: CGPoint(x: -editableControlFrame.size.width / 2.0, y: editableControlFrame.midY))
|
||||
editableControlNode.alpha = 0.0
|
||||
transition.updateAlpha(node: editableControlNode, alpha: 1.0)
|
||||
let selectableControlNode = selectableControlSizeAndApply.1(selectableControlSize, false)
|
||||
strongSelf.selectableControlNode = selectableControlNode
|
||||
strongSelf.addSubnode(selectableControlNode)
|
||||
selectableControlNode.frame = selectableControlFrame
|
||||
transition.animatePosition(node: selectableControlNode, from: CGPoint(x: -selectableControlFrame.size.width / 2.0, y: selectableControlFrame.midY))
|
||||
selectableControlNode.alpha = 0.0
|
||||
transition.updateAlpha(node: selectableControlNode, alpha: 1.0)
|
||||
} else if let selectableControlNode = strongSelf.selectableControlNode {
|
||||
transition.updateFrame(node: selectableControlNode, frame: selectableControlFrame)
|
||||
let _ = selectableControlSizeAndApply.1(selectableControlSize, transition.isAnimated)
|
||||
}
|
||||
} else if let editableControlNode = strongSelf.editableControlNode {
|
||||
} else if let selectableControlNode = strongSelf.selectableControlNode {
|
||||
crossfadeContent = true
|
||||
var editableControlFrame = editableControlNode.frame
|
||||
editableControlFrame.origin.x = -editableControlFrame.size.width
|
||||
strongSelf.editableControlNode = nil
|
||||
transition.updateAlpha(node: editableControlNode, alpha: 0.0)
|
||||
transition.updateFrame(node: editableControlNode, frame: editableControlFrame, completion: { [weak editableControlNode] _ in
|
||||
editableControlNode?.removeFromSupernode()
|
||||
var selectableControlFrame = selectableControlNode.frame
|
||||
selectableControlFrame.origin.x = -selectableControlFrame.size.width
|
||||
strongSelf.selectableControlNode = nil
|
||||
transition.updateAlpha(node: selectableControlNode, alpha: 0.0)
|
||||
transition.updateFrame(node: selectableControlNode, frame: selectableControlFrame, completion: { [weak selectableControlNode] _ in
|
||||
selectableControlNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
@ -1010,6 +1017,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.animatePosition(node: strongSelf.authorNode, from: CGPoint(x: authorPosition.x - contentDeltaX, y: authorPosition.y))
|
||||
}
|
||||
|
||||
if crossfadeContent {
|
||||
strongSelf.authorNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
strongSelf.titleNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
strongSelf.textNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
}
|
||||
|
||||
let separatorInset: CGFloat
|
||||
if (!nextIsPinned && item.index.pinningIndex != nil) || last {
|
||||
separatorInset = 0.0
|
||||
@ -1020,7 +1033,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight)))
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
if item.index.pinningIndex != nil {
|
||||
if item.selected {
|
||||
strongSelf.backgroundNode.backgroundColor = theme.itemSelectedBackgroundColor
|
||||
} else if item.index.pinningIndex != nil {
|
||||
strongSelf.backgroundNode.backgroundColor = theme.pinnedItemBackgroundColor
|
||||
} else {
|
||||
strongSelf.backgroundNode.backgroundColor = theme.itemBackgroundColor
|
||||
@ -1059,11 +1074,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if let _ = self.item, let params = self.layoutParams?.5 {
|
||||
let editingOffset: CGFloat
|
||||
if let editableControlNode = self.editableControlNode {
|
||||
editingOffset = editableControlNode.bounds.size.width
|
||||
var editableControlFrame = editableControlNode.frame
|
||||
editableControlFrame.origin.x = params.leftInset + offset
|
||||
transition.updateFrame(node: editableControlNode, frame: editableControlFrame)
|
||||
if let selectableControlNode = self.selectableControlNode {
|
||||
editingOffset = selectableControlNode.bounds.size.width
|
||||
var selectableControlFrame = selectableControlNode.frame
|
||||
selectableControlFrame.origin.x = params.leftInset + offset
|
||||
transition.updateFrame(node: selectableControlNode, frame: selectableControlFrame)
|
||||
} else {
|
||||
editingOffset = 0.0
|
||||
}
|
||||
|
||||
@ -75,7 +75,9 @@ public func chatListItemStrings(strings: PresentationStrings, message: Message?,
|
||||
if message.text.isEmpty {
|
||||
isVideo = true
|
||||
} else if #available(iOSApplicationExtension 9.0, *) {
|
||||
messageText = "📹 \(messageText)"
|
||||
if !fileMedia.isAnimated {
|
||||
messageText = "📹 \(messageText)"
|
||||
}
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +59,7 @@ final class ChatListHighlightedLocation {
|
||||
final class ChatListNodeInteraction {
|
||||
let activateSearch: () -> Void
|
||||
let peerSelected: (Peer) -> Void
|
||||
let togglePeerSelected: (PeerId) -> Void
|
||||
let messageSelected: (Message, Bool) -> Void
|
||||
let groupSelected: (PeerGroupId) -> Void
|
||||
let addContact: (String) -> Void
|
||||
@ -71,9 +72,10 @@ final class ChatListNodeInteraction {
|
||||
|
||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||
|
||||
init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, messageSelected: @escaping (Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) {
|
||||
init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) {
|
||||
self.activateSearch = activateSearch
|
||||
self.peerSelected = peerSelected
|
||||
self.togglePeerSelected = togglePeerSelected
|
||||
self.messageSelected = messageSelected
|
||||
self.groupSelected = groupSelected
|
||||
self.addContact = addContact
|
||||
@ -95,26 +97,11 @@ final class ChatListNodePeerInputActivities {
|
||||
}
|
||||
|
||||
struct ChatListNodeState: Equatable {
|
||||
let presentationData: ChatListPresentationData
|
||||
let editing: Bool
|
||||
let peerIdWithRevealedOptions: PeerId?
|
||||
let peerInputActivities: ChatListNodePeerInputActivities?
|
||||
|
||||
func withUpdatedPresentationData(_ presentationData: ChatListPresentationData) -> ChatListNodeState {
|
||||
return ChatListNodeState(presentationData: presentationData, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, peerInputActivities: self.peerInputActivities)
|
||||
}
|
||||
|
||||
func withUpdatedEditing(_ editing: Bool) -> ChatListNodeState {
|
||||
return ChatListNodeState(presentationData: self.presentationData, editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, peerInputActivities: self.peerInputActivities)
|
||||
}
|
||||
|
||||
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChatListNodeState {
|
||||
return ChatListNodeState(presentationData: self.presentationData, editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, peerInputActivities: self.peerInputActivities)
|
||||
}
|
||||
|
||||
func withUpdatedPeerInputActivities(_ peerInputActivities: ChatListNodePeerInputActivities?) -> ChatListNodeState {
|
||||
return ChatListNodeState(presentationData: self.presentationData, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, peerInputActivities: peerInputActivities)
|
||||
}
|
||||
var presentationData: ChatListPresentationData
|
||||
var editing: Bool
|
||||
var peerIdWithRevealedOptions: PeerId?
|
||||
var selectedPeerIds: Set<PeerId>
|
||||
var peerInputActivities: ChatListNodePeerInputActivities?
|
||||
|
||||
static func ==(lhs: ChatListNodeState, rhs: ChatListNodeState) -> Bool {
|
||||
if lhs.presentationData !== rhs.presentationData {
|
||||
@ -126,6 +113,9 @@ struct ChatListNodeState: Equatable {
|
||||
if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedPeerIds != rhs.selectedPeerIds {
|
||||
return false
|
||||
}
|
||||
if lhs.peerInputActivities !== rhs.peerInputActivities {
|
||||
return false
|
||||
}
|
||||
@ -136,14 +126,14 @@ struct ChatListNodeState: Equatable {
|
||||
private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||
return entries.map { entry -> ListViewInsertItem in
|
||||
switch entry.entry {
|
||||
case let .SearchEntry(theme, text):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: text, activate: {
|
||||
case let .SearchEntry(theme, text, isEnabled):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, isEnabled: isEnabled, placeholder: text, activate: {
|
||||
nodeInteraction.activateSearch()
|
||||
}), directionHint: entry.directionHint)
|
||||
case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, inputActivities, isAd):
|
||||
case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd):
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), editing: editing, hasActiveRevealControls: hasActiveRevealControls, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .peers(filter):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
@ -201,7 +191,7 @@ private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNode
|
||||
case let .HoleEntry(_, theme):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
||||
case let .GroupReferenceEntry(index, presentationData, groupId, message, topPeers, counters, editing):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, message: message, topPeers: topPeers, counters: counters), editing: editing, hasActiveRevealControls: false, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, message: message, topPeers: topPeers, counters: counters), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,14 +199,14 @@ private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNode
|
||||
private func mappedUpdateEntries(account: Account, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
return entries.map { entry -> ListViewUpdateItem in
|
||||
switch entry.entry {
|
||||
case let .SearchEntry(theme, text):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: text, activate: {
|
||||
case let .SearchEntry(theme, text, isEnabled):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, isEnabled: isEnabled, placeholder: text, activate: {
|
||||
nodeInteraction.activateSearch()
|
||||
}), directionHint: entry.directionHint)
|
||||
case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, inputActivities, isAd):
|
||||
case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd):
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), editing: editing, hasActiveRevealControls: hasActiveRevealControls, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .peers(filter):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
@ -242,7 +232,7 @@ private func mappedUpdateEntries(account: Account, nodeInteraction: ChatListNode
|
||||
case let .HoleEntry(_, theme):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
||||
case let .GroupReferenceEntry(index, presentationData, groupId, message, topPeers, counters, editing):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, message: message, topPeers: topPeers, counters: counters), editing: editing, hasActiveRevealControls: false, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, message: message, topPeers: topPeers, counters: counters), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,8 +299,11 @@ final class ChatListNode: ListView {
|
||||
private var dequeuedInitialTransitionOnLayout = false
|
||||
private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)?
|
||||
|
||||
private var currentState: ChatListNodeState
|
||||
private(set) var currentState: ChatListNodeState
|
||||
private let statePromise: ValuePromise<ChatListNodeState>
|
||||
var state: Signal<ChatListNodeState, NoError> {
|
||||
return self.statePromise.get()
|
||||
}
|
||||
|
||||
private var currentLocation: ChatListNodeLocation?
|
||||
private let chatListLocation = ValuePromise<ChatListNodeLocation>()
|
||||
@ -348,7 +341,7 @@ final class ChatListNode: ListView {
|
||||
self.controlsHistoryPreload = controlsHistoryPreload
|
||||
self.mode = mode
|
||||
|
||||
self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: false, peerIdWithRevealedOptions: nil, peerInputActivities: nil)
|
||||
self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: false, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), peerInputActivities: nil)
|
||||
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
||||
|
||||
self.theme = theme
|
||||
@ -365,6 +358,16 @@ final class ChatListNode: ListView {
|
||||
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||
peerSelected(peer.id, true, false)
|
||||
}
|
||||
}, togglePeerSelected: { [weak self] peerId in
|
||||
self?.updateState { state in
|
||||
var state = state
|
||||
if state.selectedPeerIds.contains(peerId) {
|
||||
state.selectedPeerIds.remove(peerId)
|
||||
} else {
|
||||
state.selectedPeerIds.insert(peerId)
|
||||
}
|
||||
return state
|
||||
}
|
||||
}, messageSelected: { [weak self] message, isAd in
|
||||
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||
peerSelected(message.id.peerId, true, isAd)
|
||||
@ -378,7 +381,9 @@ final class ChatListNode: ListView {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateState { state in
|
||||
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
||||
return state.withUpdatedPeerIdWithRevealedOptions(peerId)
|
||||
var state = state
|
||||
state.peerIdWithRevealedOptions = peerId
|
||||
return state
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
@ -398,8 +403,10 @@ final class ChatListNode: ListView {
|
||||
}, setPeerMuted: { [weak self] peerId, _ in
|
||||
let _ = (togglePeerMuted(account: account, peerId: peerId)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
self?.updateState {
|
||||
return $0.withUpdatedPeerIdWithRevealedOptions(nil)
|
||||
self?.updateState { state in
|
||||
var state = state
|
||||
state.peerIdWithRevealedOptions = nil
|
||||
return state
|
||||
}
|
||||
})
|
||||
}, deletePeer: { [weak self] peerId in
|
||||
@ -413,8 +420,10 @@ final class ChatListNode: ListView {
|
||||
|
||||
let _ = (togglePeerUnreadMarkInteractively(postbox: account.postbox, viewTracker: account.viewTracker, peerId: peerId)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
self?.updateState {
|
||||
return $0.withUpdatedPeerIdWithRevealedOptions(nil)
|
||||
self?.updateState { state in
|
||||
var state = state
|
||||
state.peerIdWithRevealedOptions = nil
|
||||
return state
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -443,13 +452,12 @@ final class ChatListNode: ListView {
|
||||
|
||||
let entries = chatListNodeEntriesForView(update.view, state: state, savedMessagesPeer: savedMessagesPeer, mode: mode).filter { entry in
|
||||
switch entry {
|
||||
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _):
|
||||
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _):
|
||||
//ChatListNodePeersFilter
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return true
|
||||
case let .peers(filter):
|
||||
|
||||
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
|
||||
guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false }
|
||||
guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false }
|
||||
@ -533,7 +541,7 @@ final class ChatListNode: ListView {
|
||||
var updatedPinnedCount = 0
|
||||
if let previous = previousView {
|
||||
for entry in previous.filteredEntries {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if index.pinningIndex != nil {
|
||||
previousPinnedCount += 1
|
||||
}
|
||||
@ -541,7 +549,7 @@ final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
for entry in processedView.filteredEntries {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if index.pinningIndex != nil {
|
||||
updatedPinnedCount += 1
|
||||
}
|
||||
@ -550,6 +558,9 @@ final class ChatListNode: ListView {
|
||||
if previousPinnedCount != updatedPinnedCount {
|
||||
disableAnimations = false
|
||||
}
|
||||
if previousState.selectedPeerIds != state.selectedPeerIds {
|
||||
disableAnimations = false
|
||||
}
|
||||
}
|
||||
|
||||
return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, disableAnimations: disableAnimations, account: account, scrollPosition: updatedScrollPosition)
|
||||
@ -593,7 +604,7 @@ final class ChatListNode: ListView {
|
||||
continue
|
||||
}
|
||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
||||
case let .PeerEntry(_, _, _, readState, notificationSettings, _, _, _, _, _, _, _):
|
||||
case let .PeerEntry(_, _, _, readState, notificationSettings, _, _, _, _, _, _, _, _):
|
||||
if let readState = readState {
|
||||
let count = readState.count
|
||||
rawUnreadCount += count
|
||||
@ -716,8 +727,10 @@ final class ChatListNode: ListView {
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] activities in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateState { current in
|
||||
return current.withUpdatedPeerInputActivities(activities)
|
||||
strongSelf.updateState { state in
|
||||
var state = state
|
||||
state.peerInputActivities = activities
|
||||
return state
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -725,8 +738,10 @@ final class ChatListNode: ListView {
|
||||
self.beganInteractiveDragging = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.currentState.peerIdWithRevealedOptions != nil {
|
||||
strongSelf.updateState {
|
||||
return $0.withUpdatedPeerIdWithRevealedOptions(nil)
|
||||
strongSelf.updateState { state in
|
||||
var state = state
|
||||
state.peerIdWithRevealedOptions = nil
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -740,7 +755,7 @@ final class ChatListNode: ListView {
|
||||
var referenceId: PinnedItemId?
|
||||
var beforeAll = false
|
||||
switch toEntry {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, isAd):
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, isAd):
|
||||
if isAd {
|
||||
beforeAll = true
|
||||
} else {
|
||||
@ -760,7 +775,7 @@ final class ChatListNode: ListView {
|
||||
|
||||
var itemId: PinnedItemId?
|
||||
switch fromEntry {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
itemId = .peer(index.messageIndex.id.peerId)
|
||||
case let .GroupReferenceEntry(_, _, groupId, _, _, _, _):
|
||||
itemId = .group(groupId)
|
||||
@ -860,8 +875,10 @@ final class ChatListNode: ListView {
|
||||
self.keepTopItemOverscrollBackground = ListViewKeepTopItemOverscrollBackground(color: theme.chatList.pinnedItemBackgroundColor, direction: true)
|
||||
}
|
||||
|
||||
self.updateState {
|
||||
return $0.withUpdatedPresentationData(ChatListPresentationData(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations))
|
||||
self.updateState { state in
|
||||
var state = state
|
||||
state.presentationData = ChatListPresentationData(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1080,7 +1097,7 @@ final class ChatListNode: ListView {
|
||||
continue
|
||||
}
|
||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
||||
case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _):
|
||||
case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _):
|
||||
if interaction.highlightedChatLocation?.location == ChatLocation.peer(peer.peerId) {
|
||||
current = (index, peer.peerId, entryCount - i - 1)
|
||||
break outer
|
||||
@ -1113,12 +1130,12 @@ final class ChatListNode: ListView {
|
||||
break
|
||||
case .previous(unread: false), .next(unread: false):
|
||||
if current.2 != entryCount - range.firstIndex - 1 && entryCount > 2 {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] {
|
||||
next = (index, peer.peerId)
|
||||
}
|
||||
}
|
||||
if current.2 != entryCount - range.lastIndex - 2 && entryCount > 2 {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] {
|
||||
previous = (index, peer.peerId)
|
||||
}
|
||||
}
|
||||
@ -1177,7 +1194,7 @@ final class ChatListNode: ListView {
|
||||
continue
|
||||
}
|
||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
||||
case let .PeerEntry(index, _, _, readState, notificationSettings, _, _, _, _, _, _, _):
|
||||
case let .PeerEntry(index, _, _, readState, notificationSettings, _, _, _, _, _, _, _, _):
|
||||
return index
|
||||
default:
|
||||
break
|
||||
|
||||
@ -52,8 +52,8 @@ enum ChatListNodeEntryId: Hashable {
|
||||
}
|
||||
|
||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
case SearchEntry(theme: PresentationTheme, text: String)
|
||||
case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, message: Message?, readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool)
|
||||
case SearchEntry(theme: PresentationTheme, text: String, isEnabled: Bool)
|
||||
case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, message: Message?, readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool)
|
||||
case HoleEntry(ChatListHole, theme: PresentationTheme)
|
||||
case GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, message: Message?, topPeers: [Peer], counters: GroupReferenceUnreadCounters, editing: Bool)
|
||||
|
||||
@ -61,7 +61,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
switch self {
|
||||
case .SearchEntry:
|
||||
return ChatListIndex.absoluteUpperBound
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return index
|
||||
case let .HoleEntry(hole, _):
|
||||
return ChatListIndex(pinningIndex: nil, messageIndex: hole.index)
|
||||
@ -74,7 +74,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
switch self {
|
||||
case .SearchEntry:
|
||||
return .Search
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return .PeerId(index.messageIndex.id.peerId.toInt64())
|
||||
case let .HoleEntry(hole, _):
|
||||
return .Hole(Int64(hole.index.id.id))
|
||||
@ -89,15 +89,15 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
|
||||
static func ==(lhs: ChatListNodeEntry, rhs: ChatListNodeEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .SearchEntry(lhsTheme, lhsText):
|
||||
if case let .SearchEntry(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
case let .SearchEntry(lhsTheme, lhsText, lhsEnabled):
|
||||
if case let .SearchEntry(rhsTheme, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessage, lhsUnreadCount, lhsNotificationSettings, lhsEmbeddedState, lhsPeer, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsInputActivities, lhsAd):
|
||||
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessage, lhsUnreadCount, lhsNotificationSettings, lhsEmbeddedState, lhsPeer, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd):
|
||||
switch rhs {
|
||||
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessage, rhsUnreadCount, rhsNotificationSettings, rhsEmbeddedState, rhsPeer, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsInputActivities, rhsAd):
|
||||
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessage, rhsUnreadCount, rhsNotificationSettings, rhsEmbeddedState, rhsPeer, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd):
|
||||
if lhsIndex != rhsIndex {
|
||||
return false
|
||||
}
|
||||
@ -130,6 +130,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
if lhsHasRevealControls != rhsHasRevealControls {
|
||||
return false
|
||||
}
|
||||
if lhsSelected != rhsSelected {
|
||||
return false
|
||||
}
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
@ -221,15 +224,13 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
pinnedIndexOffset = UInt16(view.additionalItemEntries.count)
|
||||
}
|
||||
|
||||
|
||||
|
||||
loop: for entry in view.entries {
|
||||
switch entry {
|
||||
case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo):
|
||||
if let savedMessagesPeer = savedMessagesPeer, savedMessagesPeer.id == index.messageIndex.id.peerId {
|
||||
continue loop
|
||||
}
|
||||
result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false))
|
||||
result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false))
|
||||
case let .HoleEntry(hole):
|
||||
result.append(.HoleEntry(hole, theme: state.presentationData.theme))
|
||||
case let .GroupReferenceEntry(groupId, index, message, topPeers, counters):
|
||||
@ -240,14 +241,14 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
}
|
||||
if view.laterIndex == nil {
|
||||
if let savedMessagesPeer = savedMessagesPeer {
|
||||
result.append(.PeerEntry(index: ChatListIndex.absoluteUpperBound.predecessor, presentationData: state.presentationData, message: nil, readState: nil, notificationSettings: nil, embeddedInterfaceState: nil, peer: RenderedPeer(peerId: savedMessagesPeer.id, peers: SimpleDictionary([savedMessagesPeer.id: savedMessagesPeer])), summaryInfo: ChatListMessageTagSummaryInfo(), editing: state.editing, hasActiveRevealControls: false, inputActivities: nil, isAd: false))
|
||||
result.append(.PeerEntry(index: ChatListIndex.absoluteUpperBound.predecessor, presentationData: state.presentationData, message: nil, readState: nil, notificationSettings: nil, embeddedInterfaceState: nil, peer: RenderedPeer(peerId: savedMessagesPeer.id, peers: SimpleDictionary([savedMessagesPeer.id: savedMessagesPeer])), summaryInfo: ChatListMessageTagSummaryInfo(), editing: state.editing, hasActiveRevealControls: false, selected: false, inputActivities: nil, isAd: false))
|
||||
} else {
|
||||
if !view.additionalItemEntries.isEmpty {
|
||||
var pinningIndex: UInt16 = UInt16(view.additionalItemEntries.count - 1)
|
||||
for entry in view.additionalItemEntries.reversed() {
|
||||
switch entry {
|
||||
case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo):
|
||||
result.append(.PeerEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: true))
|
||||
result.append(.PeerEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: true))
|
||||
if pinningIndex != 0 {
|
||||
pinningIndex -= 1
|
||||
}
|
||||
@ -258,10 +259,10 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
}
|
||||
}
|
||||
switch mode {
|
||||
case .chatList:
|
||||
result.append(.SearchEntry(theme: state.presentationData.theme, text: view.groupId == nil ? state.presentationData.strings.DialogList_SearchLabel : "Search this feed"))
|
||||
case .peers:
|
||||
result.append(.SearchEntry(theme: state.presentationData.theme, text: state.presentationData.strings.Common_Search))
|
||||
case .chatList:
|
||||
result.append(.SearchEntry(theme: state.presentationData.theme, text: view.groupId == nil ? state.presentationData.strings.DialogList_SearchLabel : "Search this feed", isEnabled: !state.editing))
|
||||
case .peers:
|
||||
result.append(.SearchEntry(theme: state.presentationData.theme, text: state.presentationData.strings.Common_Search, isEnabled: !state.editing))
|
||||
}
|
||||
}
|
||||
if result.count >= 2, case .SearchEntry = result[result.count - 1], case .HoleEntry = result[result.count - 2] {
|
||||
|
||||
@ -443,7 +443,7 @@ enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
interaction.peerSelected(peer.peer)
|
||||
})
|
||||
case let .message(message, readState, presentationData):
|
||||
return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true), editing: false, hasActiveRevealControls: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction)
|
||||
return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction)
|
||||
case let .addContact(phoneNumber, theme, strings):
|
||||
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
|
||||
interaction.addContact(phoneNumber)
|
||||
@ -743,6 +743,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
openPeer(peer, false)
|
||||
let _ = addRecentlySearchedPeer(postbox: account.postbox, peerId: peer.id).start()
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
}, togglePeerSelected: { _ in
|
||||
}, messageSelected: { [weak self] message, _ in
|
||||
if let peer = message.peers[message.id.peerId] {
|
||||
openMessage(peer, message.id)
|
||||
|
||||
@ -11,11 +11,13 @@ class ChatListSearchItem: ListViewItem {
|
||||
let selectable: Bool = false
|
||||
|
||||
let theme: PresentationTheme
|
||||
let isEnabled: Bool
|
||||
private let placeholder: String
|
||||
private let activate: () -> Void
|
||||
|
||||
init(theme: PresentationTheme, placeholder: String, activate: @escaping () -> Void) {
|
||||
init(theme: PresentationTheme, isEnabled: Bool = true, placeholder: String, activate: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.isEnabled = isEnabled
|
||||
self.placeholder = placeholder
|
||||
self.activate = activate
|
||||
}
|
||||
@ -30,7 +32,7 @@ class ChatListSearchItem: ListViewItem {
|
||||
if let nextItem = nextItem as? ChatListItem, nextItem.index.pinningIndex != nil {
|
||||
nextIsPinned = true
|
||||
}
|
||||
let (layout, apply) = makeLayout(self, params, nextIsPinned)
|
||||
let (layout, apply) = makeLayout(self, params, nextIsPinned, self.isEnabled)
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
@ -56,7 +58,7 @@ class ChatListSearchItem: ListViewItem {
|
||||
if let nextItem = nextItem as? ChatListItem, nextItem.index.pinningIndex != nil {
|
||||
nextIsPinned = true
|
||||
}
|
||||
let (nodeLayout, apply) = layout(self, params, nextIsPinned)
|
||||
let (nodeLayout, apply) = layout(self, params, nextIsPinned, self.isEnabled)
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, {
|
||||
apply(animation.isAnimated)
|
||||
@ -70,6 +72,7 @@ class ChatListSearchItem: ListViewItem {
|
||||
|
||||
class ChatListSearchItemNode: ListViewItemNode {
|
||||
let searchBarNode: SearchBarPlaceholderNode
|
||||
private var disabledOverlay: ASDisplayNode?
|
||||
var placeholder: String?
|
||||
|
||||
fileprivate var activate: (() -> Void)? {
|
||||
@ -92,17 +95,17 @@ class ChatListSearchItemNode: ListViewItemNode {
|
||||
if let nextItem = nextItem as? ChatListItem, nextItem.index.pinningIndex != nil {
|
||||
nextIsPinned = true
|
||||
}
|
||||
let (layout, apply) = makeLayout(item as! ChatListSearchItem, params, nextIsPinned)
|
||||
let (layout, apply) = makeLayout(item as! ChatListSearchItem, params, nextIsPinned, (item as! ChatListSearchItem).isEnabled)
|
||||
apply(false)
|
||||
self.contentSize = layout.contentSize
|
||||
self.insets = layout.insets
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ChatListSearchItem, _ params: ListViewItemLayoutParams, _ nextIsPinned: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
func asyncLayout() -> (_ item: ChatListSearchItem, _ params: ListViewItemLayoutParams, _ nextIsPinned: Bool, _ isEnabled: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let searchBarNodeLayout = self.searchBarNode.asyncLayout()
|
||||
let placeholder = self.placeholder
|
||||
|
||||
return { item, params, nextIsPinned in
|
||||
return { item, params, nextIsPinned, isEnabled in
|
||||
let baseWidth = params.width - params.leftInset - params.rightInset
|
||||
|
||||
let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor
|
||||
@ -120,11 +123,36 @@ class ChatListSearchItemNode: ListViewItemNode {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
strongSelf.searchBarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 8.0, y: 8.0), size: CGSize(width: baseWidth - 16.0, height: 28.0))
|
||||
let searchBarFrame = CGRect(origin: CGPoint(x: params.leftInset + 8.0, y: 8.0), size: CGSize(width: baseWidth - 16.0, height: 28.0))
|
||||
strongSelf.searchBarNode.frame = searchBarFrame
|
||||
searchBarApply()
|
||||
|
||||
strongSelf.searchBarNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - 16.0, height: 28.0))
|
||||
|
||||
if !item.isEnabled {
|
||||
if strongSelf.disabledOverlay == nil {
|
||||
let disabledOverlay = ASDisplayNode()
|
||||
strongSelf.addSubnode(disabledOverlay)
|
||||
strongSelf.disabledOverlay = disabledOverlay
|
||||
if animated {
|
||||
disabledOverlay.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
if let disabledOverlay = strongSelf.disabledOverlay {
|
||||
disabledOverlay.backgroundColor = backgroundColor.withAlphaComponent(0.4)
|
||||
disabledOverlay.frame = searchBarFrame
|
||||
}
|
||||
} else if let disabledOverlay = strongSelf.disabledOverlay {
|
||||
strongSelf.disabledOverlay = nil
|
||||
if animated {
|
||||
disabledOverlay.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak disabledOverlay] _ in
|
||||
disabledOverlay?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
disabledOverlay.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateBackgroundColor(node: strongSelf, color: backgroundColor)
|
||||
}
|
||||
})
|
||||
|
||||
56
TelegramUI/ChatListSelection.swift
Normal file
56
TelegramUI/ChatListSelection.swift
Normal file
@ -0,0 +1,56 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
enum ChatListSelectionReadOption: Equatable {
|
||||
case all(enabled: Bool)
|
||||
case selective(enabled: Bool)
|
||||
}
|
||||
|
||||
struct ChatListSelectionOptions: Equatable {
|
||||
let read: ChatListSelectionReadOption
|
||||
let delete: Bool
|
||||
}
|
||||
|
||||
func chatListSelectionOptions(postbox: Postbox, peerIds: Set<PeerId>) -> Signal<ChatListSelectionOptions, NoError> {
|
||||
if peerIds.isEmpty {
|
||||
let key = PostboxViewKey.unreadCounts(items: [.total(nil)])
|
||||
return postbox.combinedView(keys: [key])
|
||||
|> map { view -> ChatListSelectionOptions in
|
||||
var hasUnread = false
|
||||
if let unreadCounts = view.views[key] as? UnreadMessageCountsView, let total = unreadCounts.total() {
|
||||
for (_, counter) in total.1.absoluteCounters {
|
||||
if counter.messageCount != 0 {
|
||||
hasUnread = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ChatListSelectionOptions(read: .all(enabled: hasUnread), delete: false)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
let items: [UnreadMessageCountsItem] = peerIds.map(UnreadMessageCountsItem.peer)
|
||||
let key = PostboxViewKey.unreadCounts(items: items)
|
||||
return postbox.combinedView(keys: [key])
|
||||
|> map { view -> ChatListSelectionOptions in
|
||||
var hasUnread = false
|
||||
if let unreadCounts = view.views[key] as? UnreadMessageCountsView {
|
||||
loop: for entry in unreadCounts.entries {
|
||||
switch entry {
|
||||
case let .peer(_, count):
|
||||
if count != 0 {
|
||||
hasUnread = true
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ChatListSelectionOptions(read: .selective(enabled: hasUnread), delete: true)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
}
|
||||
@ -137,7 +137,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane {
|
||||
|
||||
let interaction = TrendingPaneInteraction(installPack: { [weak self] info in
|
||||
if let strongSelf = self, let info = info as? StickerPackCollectionInfo {
|
||||
let _ = (loadedStickerPack(postbox: strongSelf.account.postbox, network: strongSelf.account.network, reference: .id(id: info.id.id, accessHash: info.accessHash))
|
||||
let _ = (loadedStickerPack(postbox: strongSelf.account.postbox, network: strongSelf.account.network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
switch result {
|
||||
case let .result(info, items, installed):
|
||||
|
||||
@ -178,7 +178,14 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
var unboundSize: CGSize
|
||||
if let image = media as? TelegramMediaImage, let dimensions = largestImageRepresentation(image.representations)?.dimensions {
|
||||
unboundSize = CGSize(width: floor(dimensions.width * 0.5), height: floor(dimensions.height * 0.5))
|
||||
} else if let file = media as? TelegramMediaFile, let dimensions = file.dimensions {
|
||||
} else if let file = media as? TelegramMediaFile, var dimensions = file.dimensions {
|
||||
if let thumbnail = file.previewRepresentations.first {
|
||||
let dimensionsVertical = dimensions.width < dimensions.height
|
||||
let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height
|
||||
if dimensionsVertical != thumbnailVertical {
|
||||
dimensions = CGSize(width: dimensions.height, height: dimensions.width)
|
||||
}
|
||||
}
|
||||
unboundSize = CGSize(width: floor(dimensions.width * 0.5), height: floor(dimensions.height * 0.5))
|
||||
if file.isAnimated {
|
||||
unboundSize = unboundSize.aspectFilled(CGSize(width: 480.0, height: 480.0))
|
||||
|
||||
@ -109,7 +109,8 @@ private let chatList = PresentationThemeChatList(
|
||||
itemSeparatorColor: UIColor(rgb: 0x131A23),
|
||||
itemBackgroundColor: UIColor(rgb: 0x18222D),
|
||||
pinnedItemBackgroundColor: UIColor(rgb: 0x213040),
|
||||
itemHighlightedBackgroundColor: UIColor(rgb: 0x10171F), //!!!
|
||||
itemHighlightedBackgroundColor: UIColor(rgb: 0x10171F),
|
||||
itemSelectedBackgroundColor: UIColor(rgb: 0x10171F),
|
||||
titleColor: UIColor(rgb: 0xffffff),
|
||||
secretTitleColor: secretColor,
|
||||
dateTextColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
|
||||
|
||||
@ -110,6 +110,7 @@ private let chatList = PresentationThemeChatList(
|
||||
itemBackgroundColor: UIColor(rgb: 0x000000),
|
||||
pinnedItemBackgroundColor: UIColor(rgb: 0x1c1c1d),
|
||||
itemHighlightedBackgroundColor: UIColor(rgb: 0x191919),
|
||||
itemSelectedBackgroundColor: UIColor(rgb: 0x191919),
|
||||
titleColor: UIColor(rgb: 0xffffff),
|
||||
secretTitleColor: UIColor(rgb: 0xb2b2b2), //!!!
|
||||
dateTextColor: UIColor(rgb: 0x8e8e93),
|
||||
|
||||
@ -110,6 +110,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
|
||||
itemBackgroundColor: .white,
|
||||
pinnedItemBackgroundColor: UIColor(rgb: 0xf7f7f7),
|
||||
itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9),
|
||||
itemSelectedBackgroundColor: UIColor(rgb: 0xe9f0fa),
|
||||
titleColor: .black,
|
||||
secretTitleColor: secretColor,
|
||||
dateTextColor: UIColor(rgb: 0x8e8e93),
|
||||
@ -140,6 +141,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
|
||||
itemBackgroundColor: .white,
|
||||
pinnedItemBackgroundColor: UIColor(rgb: 0xf7f7f7),
|
||||
itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9),
|
||||
itemSelectedBackgroundColor: UIColor(rgb: 0xe9f0fa),
|
||||
titleColor: .black,
|
||||
secretTitleColor: secretColor,
|
||||
dateTextColor: UIColor(rgb: 0x8e8e93),
|
||||
|
||||
@ -171,7 +171,7 @@ public func featuredStickerPacksController(account: Account) -> ViewController {
|
||||
let arguments = FeaturedStickerPacksControllerArguments(account: account, openStickerPack: { info in
|
||||
presentStickerPackController?(info)
|
||||
}, addPack: { info in
|
||||
let _ = (loadedStickerPack(postbox: account.postbox, network: account.network, reference: .id(id: info.id.id, accessHash: info.accessHash))
|
||||
let _ = (loadedStickerPack(postbox: account.postbox, network: account.network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
switch result {
|
||||
case let .result(info, items, installed):
|
||||
|
||||
@ -309,7 +309,7 @@ public func groupStickerPackSetupController(account: Account, peerId: PeerId, cu
|
||||
|
||||
let initialData = Promise<InitialStickerPackData?>()
|
||||
if let currentPackInfo = currentPackInfo {
|
||||
initialData.set(cachedStickerPack(postbox: account.postbox, network: account.network, reference: .id(id: currentPackInfo.id.id, accessHash: currentPackInfo.accessHash))
|
||||
initialData.set(cachedStickerPack(postbox: account.postbox, network: account.network, reference: .id(id: currentPackInfo.id.id, accessHash: currentPackInfo.accessHash), forceRemote: false)
|
||||
|> map { result -> InitialStickerPackData? in
|
||||
switch result {
|
||||
case .none:
|
||||
@ -349,7 +349,7 @@ public func groupStickerPackSetupController(account: Account, peerId: PeerId, cu
|
||||
}
|
||||
}
|
||||
return .single((searchText, .searching))
|
||||
|> then((loadedStickerPack(postbox: account.postbox, network: account.network, reference: .name(searchText.lowercased())) |> delay(0.3, queue: Queue.concurrentDefaultQueue()))
|
||||
|> then((loadedStickerPack(postbox: account.postbox, network: account.network, reference: .name(searchText.lowercased()), forceActualized: false) |> delay(0.3, queue: Queue.concurrentDefaultQueue()))
|
||||
|> mapToSignal { value -> Signal<(String, GroupStickerPackSearchState), NoError> in
|
||||
switch value {
|
||||
case .fetching:
|
||||
|
||||
@ -43,7 +43,7 @@ final class HashtagSearchController: TelegramController {
|
||||
}
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {
|
||||
}, peerSelected: { peer in
|
||||
|
||||
}, togglePeerSelected: { _ in
|
||||
}, messageSelected: { [weak self] message, _ in
|
||||
if let strongSelf = self {
|
||||
if let peer = message.peers[message.id.peerId] {
|
||||
|
||||
@ -14,8 +14,8 @@ final class ItemListSelectableControlNode: ASDisplayNode {
|
||||
self.addSubnode(self.checkNode)
|
||||
}
|
||||
|
||||
static func asyncLayout(_ node: ItemListSelectableControlNode?) -> (_ strokeColor: UIColor, _ fillColor: UIColor, _ foregroundColor: UIColor, _ selected: Bool) -> (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode) {
|
||||
return { strokeColor, fillColor, foregroundColor, selected in
|
||||
static func asyncLayout(_ node: ItemListSelectableControlNode?) -> (_ strokeColor: UIColor, _ fillColor: UIColor, _ foregroundColor: UIColor, _ selected: Bool, _ compact: Bool) -> (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode) {
|
||||
return { strokeColor, fillColor, foregroundColor, selected, compact in
|
||||
let resultNode: ItemListSelectableControlNode
|
||||
if let node = node {
|
||||
resultNode = node
|
||||
@ -23,10 +23,9 @@ final class ItemListSelectableControlNode: ASDisplayNode {
|
||||
resultNode = ItemListSelectableControlNode(strokeColor: strokeColor, fillColor: fillColor, foregroundColor: foregroundColor)
|
||||
}
|
||||
|
||||
return (45.0, { size, animated in
|
||||
|
||||
return (compact ? 38.0 : 45.0, { size, animated in
|
||||
let checkSize = CGSize(width: 32.0, height: 32.0)
|
||||
resultNode.checkNode.frame = CGRect(origin: CGPoint(x: 12.0, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize)
|
||||
resultNode.checkNode.frame = CGRect(origin: CGPoint(x: compact ? 9 : 12.0, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize)
|
||||
resultNode.checkNode.setIsChecked(selected, animated: animated)
|
||||
return resultNode
|
||||
})
|
||||
|
||||
@ -176,7 +176,7 @@ class ItemListTextWithLabelItemNode: ListViewItemNode {
|
||||
var leftOffset: CGFloat = 0.0
|
||||
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||
if let selected = item.selected {
|
||||
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected)
|
||||
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false)
|
||||
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
|
||||
leftOffset += selectionWidth - 8.0
|
||||
}
|
||||
|
||||
@ -304,7 +304,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
var leftOffset: CGFloat = 0.0
|
||||
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||
if case let .selectable(selected) = item.selection {
|
||||
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected)
|
||||
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false)
|
||||
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
|
||||
leftOffset += selectionWidth
|
||||
}
|
||||
|
||||
@ -153,7 +153,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
|
||||
var leftOffset: CGFloat = 0.0
|
||||
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||
if case let .selectable(selected) = item.selection {
|
||||
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected)
|
||||
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false)
|
||||
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
|
||||
leftOffset += selectionWidth
|
||||
}
|
||||
|
||||
@ -55,7 +55,19 @@ final class NativeVideoContent: UniversalVideoContent {
|
||||
self.nativeId = id
|
||||
self.fileReference = fileReference
|
||||
self.imageReference = imageReference
|
||||
self.dimensions = fileReference.media.dimensions ?? CGSize(width: 128.0, height: 128.0)
|
||||
if var dimensions = fileReference.media.dimensions {
|
||||
if let thumbnail = fileReference.media.previewRepresentations.first {
|
||||
let dimensionsVertical = dimensions.width < dimensions.height
|
||||
let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height
|
||||
if dimensionsVertical != thumbnailVertical {
|
||||
dimensions = CGSize(width: dimensions.height, height: dimensions.width)
|
||||
}
|
||||
}
|
||||
self.dimensions = dimensions
|
||||
} else {
|
||||
self.dimensions = CGSize(width: 128.0, height: 128.0)
|
||||
}
|
||||
|
||||
self.duration = fileReference.media.duration ?? 0
|
||||
self.streamVideo = streamVideo
|
||||
self.loopVideo = loopVideo
|
||||
|
||||
@ -71,7 +71,7 @@ final class OverlayInstantVideoDecoration: UniversalVideoDecoration {
|
||||
if let snapshot = snapshot {
|
||||
self.contentContainerNode.view.addSubview(snapshot)
|
||||
if let validLayoutSize = self.validLayoutSize {
|
||||
snapshot.frame = CGRect(origin: CGPoint(), size: validLayoutSize)
|
||||
snapshot.frame = CGRect(origin: CGPoint(), size: snapshot.frame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,7 +129,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
||||
|
||||
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: theme.list.controlSecondaryColor, foregroundColor: theme.list.itemAccentColor))
|
||||
self.leftDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor)
|
||||
self.leftDurationLabel.displaysAsynchronously = false
|
||||
self.rightDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor)
|
||||
self.rightDurationLabel.displaysAsynchronously = false
|
||||
self.rightDurationLabel.mode = .reversed
|
||||
self.rightDurationLabel.alignment = .right
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ private enum PeerReportOption {
|
||||
case violence
|
||||
case copyright
|
||||
case pornoghraphy
|
||||
case childAbuse
|
||||
case other
|
||||
}
|
||||
|
||||
@ -25,6 +26,7 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
|
||||
.spam,
|
||||
.violence,
|
||||
.pornoghraphy,
|
||||
.childAbuse,
|
||||
.copyright,
|
||||
.other
|
||||
]
|
||||
@ -32,6 +34,7 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
|
||||
var items: [ActionSheetItem] = []
|
||||
for option in options {
|
||||
let title: String
|
||||
var color: ActionSheetButtonColor = .accent
|
||||
switch option {
|
||||
case .spam:
|
||||
title = presentationData.strings.ReportPeer_ReasonSpam
|
||||
@ -39,13 +42,15 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
|
||||
title = presentationData.strings.ReportPeer_ReasonViolence
|
||||
case .pornoghraphy:
|
||||
title = presentationData.strings.ReportPeer_ReasonPornography
|
||||
case .childAbuse:
|
||||
title = presentationData.strings.ReportPeer_ReasonChildAbuse
|
||||
color = .destructive
|
||||
case .copyright:
|
||||
title = presentationData.strings.ReportPeer_ReasonCopyright
|
||||
case .other:
|
||||
title = presentationData.strings.ReportPeer_ReasonOther
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: title, action: { [weak controller] in
|
||||
//account.reportPeer#ae189d5f peer:InputPeer reason:ReportReason = Bool;
|
||||
items.append(ActionSheetButtonItem(title: title, color: color, action: { [weak controller] in
|
||||
var reportReason: ReportReason?
|
||||
switch option {
|
||||
case .spam:
|
||||
@ -54,8 +59,10 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
|
||||
reportReason = .violence
|
||||
case .pornoghraphy:
|
||||
reportReason = .porno
|
||||
case .copyright:
|
||||
reportReason = .copyright
|
||||
case .childAbuse:
|
||||
reportReason = .childAbuse
|
||||
case .copyright:
|
||||
reportReason = .copyright
|
||||
case .other:
|
||||
break
|
||||
}
|
||||
|
||||
@ -312,6 +312,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)? = nil) {
|
||||
self.clipsToBounds = true
|
||||
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, additive: true, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -333,6 +333,7 @@ public final class PresentationThemeChatList {
|
||||
public let itemBackgroundColor: UIColor
|
||||
public let pinnedItemBackgroundColor: UIColor
|
||||
public let itemHighlightedBackgroundColor: UIColor
|
||||
public let itemSelectedBackgroundColor: UIColor
|
||||
public let titleColor: UIColor
|
||||
public let secretTitleColor: UIColor
|
||||
public let dateTextColor: UIColor
|
||||
@ -356,12 +357,13 @@ public final class PresentationThemeChatList {
|
||||
public let verifiedIconForegroundColor: UIColor
|
||||
public let secretIconColor: UIColor
|
||||
|
||||
init(backgroundColor: UIColor, itemSeparatorColor: UIColor, itemBackgroundColor: UIColor, pinnedItemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, titleColor: UIColor, secretTitleColor: UIColor, dateTextColor: UIColor, authorNameColor: UIColor, messageTextColor: UIColor, messageDraftTextColor: UIColor, checkmarkColor: UIColor, pendingIndicatorColor: UIColor, muteIconColor: UIColor, unreadBadgeActiveBackgroundColor: UIColor, unreadBadgeActiveTextColor: UIColor, unreadBadgeInactiveBackgroundColor: UIColor, unreadBadgeInactiveTextColor: UIColor, pinnedBadgeColor: UIColor, pinnedSearchBarColor: UIColor, regularSearchBarColor: UIColor, sectionHeaderFillColor: UIColor, sectionHeaderTextColor: UIColor, searchBarKeyboardColor: PresentationThemeKeyboardColor, verifiedIconFillColor: UIColor, verifiedIconForegroundColor: UIColor, secretIconColor: UIColor) {
|
||||
init(backgroundColor: UIColor, itemSeparatorColor: UIColor, itemBackgroundColor: UIColor, pinnedItemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, itemSelectedBackgroundColor: UIColor, titleColor: UIColor, secretTitleColor: UIColor, dateTextColor: UIColor, authorNameColor: UIColor, messageTextColor: UIColor, messageDraftTextColor: UIColor, checkmarkColor: UIColor, pendingIndicatorColor: UIColor, muteIconColor: UIColor, unreadBadgeActiveBackgroundColor: UIColor, unreadBadgeActiveTextColor: UIColor, unreadBadgeInactiveBackgroundColor: UIColor, unreadBadgeInactiveTextColor: UIColor, pinnedBadgeColor: UIColor, pinnedSearchBarColor: UIColor, regularSearchBarColor: UIColor, sectionHeaderFillColor: UIColor, sectionHeaderTextColor: UIColor, searchBarKeyboardColor: PresentationThemeKeyboardColor, verifiedIconFillColor: UIColor, verifiedIconForegroundColor: UIColor, secretIconColor: UIColor) {
|
||||
self.backgroundColor = backgroundColor
|
||||
self.itemSeparatorColor = itemSeparatorColor
|
||||
self.itemBackgroundColor = itemBackgroundColor
|
||||
self.pinnedItemBackgroundColor = pinnedItemBackgroundColor
|
||||
self.itemHighlightedBackgroundColor = itemHighlightedBackgroundColor
|
||||
self.itemSelectedBackgroundColor = itemSelectedBackgroundColor
|
||||
self.titleColor = titleColor
|
||||
self.secretTitleColor = secretTitleColor
|
||||
self.dateTextColor = dateTextColor
|
||||
|
||||
Binary file not shown.
@ -56,7 +56,7 @@ final class StickerPackPreviewController: ViewController {
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
self.stickerPackContents.set(loadedStickerPack(postbox: account.postbox, network: account.network, reference: stickerPack))
|
||||
self.stickerPackContents.set(loadedStickerPack(postbox: account.postbox, network: account.network, reference: stickerPack, forceActualized: true))
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
|
||||
@ -5,6 +5,37 @@ import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let stickerItem: StickerPackItem
|
||||
|
||||
var stableId: MediaId {
|
||||
return self.stickerItem.file.fileId
|
||||
}
|
||||
|
||||
static func <(lhs: StickerPackPreviewGridEntry, rhs: StickerPackPreviewGridEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(account: Account, interaction: StickerPackPreviewInteraction) -> StickerPackPreviewGridItem {
|
||||
return StickerPackPreviewGridItem(account: account, stickerItem: self.stickerItem, interaction: interaction)
|
||||
}
|
||||
}
|
||||
|
||||
private struct StickerPackPreviewGridTransaction {
|
||||
let deletions: [Int]
|
||||
let insertions: [GridNodeInsertItem]
|
||||
let updates: [GridNodeUpdateItem]
|
||||
|
||||
init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], account: Account, interaction: StickerPackPreviewInteraction) {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list)
|
||||
|
||||
self.deletions = deleteIndices
|
||||
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction), previousIndex: $0.2) }
|
||||
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction)) }
|
||||
}
|
||||
}
|
||||
|
||||
final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private let account: Account
|
||||
private let openShare: () -> Void
|
||||
@ -42,7 +73,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
private var stickerPackUpdated = false
|
||||
private var stickerPackInitiallyInstalled : Bool?
|
||||
|
||||
private var didSetItems = false
|
||||
private var currentItems: [StickerPackPreviewGridEntry] = []
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
@ -267,7 +298,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
|
||||
let contentFrame = contentContainerFrame.insetBy(dx: 12.0, dy: 0.0)
|
||||
|
||||
var insertItems: [GridNodeInsertItem] = []
|
||||
var transaction: StickerPackPreviewGridTransaction?
|
||||
|
||||
var itemCount = 0
|
||||
var animateIn = false
|
||||
@ -286,16 +317,21 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
self.activityIndicator = nil
|
||||
}
|
||||
itemCount = items.count
|
||||
if !self.didSetItems {
|
||||
let entities = generateTextEntities(info.title, enabledTypes: [.mention])
|
||||
self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: Font.medium(20.0), linkFont: Font.medium(20.0), boldFont: Font.medium(20.0), italicFont: Font.medium(20.0), fixedFont: Font.medium(20.0))
|
||||
|
||||
self.didSetItems = true
|
||||
animateIn = true
|
||||
for i in 0 ..< items.count {
|
||||
insertItems.append(GridNodeInsertItem(index: i, item: StickerPackPreviewGridItem(account: self.account, stickerItem: items[i] as! StickerPackItem, interaction: self.interaction), previousIndex: nil))
|
||||
|
||||
var updatedItems: [StickerPackPreviewGridEntry] = []
|
||||
for item in items {
|
||||
if let item = item as? StickerPackItem {
|
||||
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item))
|
||||
}
|
||||
}
|
||||
|
||||
if self.currentItems.isEmpty && !updatedItems.isEmpty {
|
||||
let entities = generateTextEntities(info.title, enabledTypes: [.mention])
|
||||
self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: Font.medium(20.0), linkFont: Font.medium(20.0), boldFont: Font.medium(20.0), italicFont: Font.medium(20.0), fixedFont: Font.medium(20.0))
|
||||
animateIn = true
|
||||
}
|
||||
transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, account: self.account, interaction: self.interaction)
|
||||
self.currentItems = updatedItems
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,7 +371,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
|
||||
let gridSize = CGSize(width: contentFrame.size.width, height: max(32.0, contentFrame.size.height - titleAreaHeight))
|
||||
|
||||
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: insertItems, updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transaction?.deletions ?? [], insertItems: transaction?.insertions ?? [], updateItems: transaction?.updates ?? [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||
transition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
|
||||
|
||||
if animateIn {
|
||||
|
||||
@ -217,7 +217,7 @@ final class StickerPaneSearchContainerNode: ASDisplayNode {
|
||||
}
|
||||
}, install: { [weak self] info in
|
||||
if let strongSelf = self {
|
||||
let _ = (loadedStickerPack(postbox: strongSelf.account.postbox, network: strongSelf.account.network, reference: .id(id: info.id.id, accessHash: info.accessHash))
|
||||
let _ = (loadedStickerPack(postbox: strongSelf.account.postbox, network: strongSelf.account.network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
switch result {
|
||||
case let .result(info, items, installed):
|
||||
|
||||
@ -147,19 +147,32 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
|
||||
case let .peerName(name, parameter):
|
||||
return resolvePeerByName(account: account, name: name)
|
||||
|> take(1)
|
||||
|> map { peerId -> ResolvedUrl? in
|
||||
if let peerId = peerId {
|
||||
|> mapToSignal { peerId -> Signal<Peer?, NoError> in
|
||||
return account.postbox.transaction { transaction -> Peer? in
|
||||
if let peerId = peerId {
|
||||
return transaction.getPeer(peerId)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|> map { peer -> ResolvedUrl? in
|
||||
if let peer = peer {
|
||||
if let parameter = parameter {
|
||||
switch parameter {
|
||||
case let .botStart(payload):
|
||||
return .botStart(peerId: peerId, payload: payload)
|
||||
return .botStart(peerId: peer.id, payload: payload)
|
||||
case let .groupBotStart(payload):
|
||||
return .groupBotStart(peerId: peerId, payload: payload)
|
||||
return .groupBotStart(peerId: peer.id, payload: payload)
|
||||
case let .channelMessage(id):
|
||||
return .channelMessage(peerId: peerId, messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id))
|
||||
return .channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id))
|
||||
}
|
||||
} else {
|
||||
return .peer(peerId, .chat(textInputState: nil, messageId: nil))
|
||||
if let peer = peer as? TelegramUser, peer.botInfo != nil {
|
||||
return .peer(peer.id, .info)
|
||||
} else {
|
||||
return .peer(peer.id, .chat(textInputState: nil, messageId: nil))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user