Support for chat list editing actions

This commit is contained in:
Peter 2018-11-27 17:30:17 +03:00
parent 394b204f29
commit e9da154ac3
35 changed files with 1818 additions and 1481 deletions

View File

@ -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 */,

View File

@ -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):

View File

@ -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 {

View File

@ -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))
}
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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] {

View File

@ -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)

View File

@ -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)
}
})

View 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
}
}

View File

@ -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):

View File

@ -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))

View File

@ -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),

View File

@ -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),

View File

@ -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),

View File

@ -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):

View File

@ -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:

View File

@ -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] {

View File

@ -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
})

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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):

View File

@ -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